summaryrefslogtreecommitdiff
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.c319
1 files changed, 187 insertions, 132 deletions
diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c
index 1fbf6ba585..c7d9dcf4e2 100644
--- a/src/login/pam_systemd.c
+++ b/src/login/pam_systemd.c
@@ -16,6 +16,7 @@
#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-util.h"
+#include "cgroup-util.h"
#include "def.h"
#include "fd-util.h"
#include "fileio.h"
@@ -24,19 +25,19 @@
#include "login-util.h"
#include "macro.h"
#include "parse-util.h"
+#include "path-util.h"
#include "process-util.h"
#include "socket-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "util.h"
-#include "path-util.h"
-#include "cgroup-util.h"
static int parse_argv(
pam_handle_t *handle,
int argc, const char **argv,
const char **class,
const char **type,
+ const char **desktop,
bool *debug) {
unsigned i;
@@ -53,6 +54,10 @@ static int parse_argv(
if (type)
*type = argv[i] + 5;
+ } else if (startswith(argv[i], "desktop=")) {
+ if (desktop)
+ *desktop = argv[i] + 8;
+
} else if (streq(argv[i], "debug")) {
if (debug)
*debug = true;
@@ -109,14 +114,37 @@ static int get_user_data(
return PAM_SUCCESS;
}
+static int socket_from_display(const char *display, char **path) {
+ size_t k;
+ char *f, *c;
+
+ assert(display);
+ assert(path);
+
+ if (!display_is_local(display))
+ return -EINVAL;
+
+ k = strspn(display+1, "0123456789");
+
+ f = new(char, STRLEN("/tmp/.X11-unix/X") + k + 1);
+ if (!f)
+ return -ENOMEM;
+
+ c = stpcpy(f, "/tmp/.X11-unix/X");
+ memcpy(c, display+1, k);
+ c[k] = 0;
+
+ *path = f;
+
+ return 0;
+}
+
static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
- union sockaddr_union sa = {
- .un.sun_family = AF_UNIX,
- };
+ union sockaddr_union sa = {};
_cleanup_free_ char *p = NULL, *tty = NULL;
_cleanup_close_ int fd = -1;
struct ucred ucred;
- int v, r;
+ int v, r, salen;
assert(display);
assert(vtnr);
@@ -130,13 +158,15 @@ static int get_seat_from_display(const char *display, const char **seat, uint32_
r = socket_from_display(display, &p);
if (r < 0)
return r;
- strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
+ salen = sockaddr_un_set_path(&sa.un, p);
+ if (salen < 0)
+ return salen;
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (fd < 0)
return -errno;
- if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
+ if (connect(fd, &sa.sa, salen) < 0)
return -errno;
r = getpeercred(fd, &ucred);
@@ -160,40 +190,6 @@ static int get_seat_from_display(const char *display, const char **seat, uint32_
return 0;
}
-static int export_legacy_dbus_address(
- pam_handle_t *handle,
- uid_t uid,
- const char *runtime) {
-
- _cleanup_free_ char *s = NULL;
- int r = PAM_BUF_ERR;
-
- /* FIXME: We *really* should move the access() check into the
- * daemons that spawn dbus-daemon, instead of forcing
- * DBUS_SESSION_BUS_ADDRESS= here. */
-
- s = strjoin(runtime, "/bus");
- if (!s)
- goto error;
-
- if (access(s, F_OK) < 0)
- return PAM_SUCCESS;
-
- s = mfree(s);
- if (asprintf(&s, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
- goto error;
-
- r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
- if (r != PAM_SUCCESS)
- goto error;
-
- return PAM_SUCCESS;
-
-error:
- pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
- return r;
-}
-
static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
uint64_t val;
int r;
@@ -208,9 +204,9 @@ static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, co
return r;
}
} else {
- r = parse_percent(limit);
+ r = parse_permille(limit);
if (r >= 0) {
- r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
+ r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", (uint32_t) (((uint64_t) r * UINT32_MAX) / 1000U));
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror(-r));
return r;
@@ -231,8 +227,7 @@ static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, co
return 0;
}
-static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit)
-{
+static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
uint64_t val;
int r;
@@ -257,23 +252,92 @@ static int append_session_cg_weight(pam_handle_t *handle, sd_bus_message *m, con
uint64_t val;
int r;
- if (!isempty(limit)) {
- r = cg_weight_parse(limit, &val);
- if (r >= 0) {
- r = sd_bus_message_append(m, "(sv)", field, "t", val);
- if (r < 0) {
- pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror(-r));
- return r;
- }
- } else if (streq(field, "CPUWeight"))
- pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight: %s, ignoring.", limit);
- else
- pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight: %s, ignoring.", limit);
- }
+ if (isempty(limit))
+ return 0;
+
+ r = cg_weight_parse(limit, &val);
+ if (r >= 0) {
+ r = sd_bus_message_append(m, "(sv)", field, "t", val);
+ if (r < 0) {
+ pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror(-r));
+ return r;
+ }
+ } else if (streq(field, "CPUWeight"))
+ pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight: %s, ignoring.", limit);
+ else
+ pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight: %s, ignoring.", limit);
return 0;
}
+static const char* getenv_harder(pam_handle_t *handle, const char *key, const char *fallback) {
+ const char *v;
+
+ assert(handle);
+ assert(key);
+
+ /* Looks for an environment variable, preferrably in the environment block associated with the specified PAM
+ * handle, falling back to the process' block instead. */
+
+ v = pam_getenv(handle, key);
+ if (!isempty(v))
+ return v;
+
+ v = getenv(key);
+ if (!isempty(v))
+ return v;
+
+ return fallback;
+}
+
+static int update_environment(pam_handle_t *handle, const char *key, const char *value) {
+ int r;
+
+ assert(handle);
+ assert(key);
+
+ /* Updates the environment, but only if there's actually a value set. Also, log about errors */
+
+ if (isempty(value))
+ return PAM_SUCCESS;
+
+ r = pam_misc_setenv(handle, key, value, 0);
+ if (r != PAM_SUCCESS)
+ pam_syslog(handle, LOG_ERR, "Failed to set environment variable %s.", key);
+
+ return r;
+}
+
+static bool validate_runtime_directory(pam_handle_t *handle, const char *path, uid_t uid) {
+ struct stat st;
+
+ assert(path);
+
+ /* Just some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually set
+ * up properly for us. */
+
+ if (lstat(path, &st) < 0) {
+ pam_syslog(handle, LOG_ERR, "Failed to stat() runtime directory '%s': %s", path, strerror(errno));
+ goto fail;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path);
+ goto fail;
+ }
+
+ if (st.st_uid != uid) {
+ pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid);
+ goto fail;
+ }
+
+ return true;
+
+fail:
+ pam_syslog(handle, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order.");
+ return false;
+}
+
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle,
int flags,
@@ -288,7 +352,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
*remote_user = NULL, *remote_host = NULL,
*seat = NULL,
*type = NULL, *class = NULL,
- *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL,
+ *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = NULL,
*memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int session_fd = -1, existing, r;
@@ -307,6 +371,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
argc, argv,
&class_pam,
&type_pam,
+ &desktop_pam,
&debug) < 0)
return PAM_SESSION_ERR;
@@ -332,16 +397,14 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (asprintf(&rt, "/run/user/"UID_FMT, pw->pw_uid) < 0)
return PAM_BUF_ERR;
- r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
- if (r != PAM_SUCCESS) {
- pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
- return r;
+ if (validate_runtime_directory(handle, rt, pw->pw_uid)) {
+ r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
+ if (r != PAM_SUCCESS) {
+ pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
+ return r;
+ }
}
- r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
- if (r != PAM_SUCCESS)
- return r;
-
return PAM_SUCCESS;
}
@@ -352,55 +415,41 @@ _public_ PAM_EXTERN int pam_sm_open_session(
pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
- seat = pam_getenv(handle, "XDG_SEAT");
- if (isempty(seat))
- seat = getenv("XDG_SEAT");
-
- cvtnr = pam_getenv(handle, "XDG_VTNR");
- if (isempty(cvtnr))
- cvtnr = getenv("XDG_VTNR");
-
- type = pam_getenv(handle, "XDG_SESSION_TYPE");
- if (isempty(type))
- type = getenv("XDG_SESSION_TYPE");
- if (isempty(type))
- type = type_pam;
-
- class = pam_getenv(handle, "XDG_SESSION_CLASS");
- if (isempty(class))
- class = getenv("XDG_SESSION_CLASS");
- if (isempty(class))
- class = class_pam;
-
- desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
- if (isempty(desktop))
- desktop = getenv("XDG_SESSION_DESKTOP");
+ 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);
tty = strempty(tty);
if (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. */
-
+ /* 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")) {
- /* cron has been setting PAM_TTY to "cron" for a very
- * long time and it probably shouldn't stop doing that
- * for compatibility reasons. */
+ /* 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
+ * off processes.) */
type = "unspecified";
class = "background";
tty = NULL;
+
} else if (streq(tty, "ssh")) {
- /* ssh has been setting PAM_TTY to "ssh" for a very
- * long time and probably shouldn't stop doing that
- * for compatibility reasons. */
+ /* 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";
class = "user";
- tty = NULL;
+ 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
+ * does the PAM session registration early for new connections, and registers a pty only
+ * 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
/* Chop off leading /dev prefix that some clients specify, but others do not. */
tty = skip_dev_prefix(tty);
@@ -411,13 +460,14 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (!isempty(display) && !vtnr) {
if (isempty(seat))
- get_seat_from_display(display, &seat, &vtnr);
+ (void) get_seat_from_display(display, &seat, &vtnr);
else if (streq(seat, "seat0"))
- get_seat_from_display(display, NULL, &vtnr);
+ (void) get_seat_from_display(display, NULL, &vtnr);
}
if (seat && !streq(seat, "seat0") && vtnr != 0) {
- pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
+ if (debug)
+ pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
vtnr = 0;
}
@@ -470,7 +520,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
r = sd_bus_message_append(m, "uusssssussbss",
(uint32_t) pw->pw_uid,
- (uint32_t) getpid_cached(),
+ 0,
service,
type,
class,
@@ -518,7 +568,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0) {
if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
- pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
+ if (debug)
+ pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
return PAM_SUCCESS;
} else {
pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
@@ -546,11 +597,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);
- r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
- if (r != PAM_SUCCESS) {
- pam_syslog(handle, LOG_ERR, "Failed to set session id.");
+ r = update_environment(handle, "XDG_SESSION_ID", id);
+ if (r != PAM_SUCCESS)
return r;
- }
if (original_uid == pw->pw_uid) {
/* Don't set $XDG_RUNTIME_DIR if the user we now
@@ -559,34 +608,40 @@ _public_ PAM_EXTERN int pam_sm_open_session(
* in privileged apps clobbering the runtime directory
* unnecessarily. */
- r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
- if (r != PAM_SUCCESS) {
- pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
- return r;
+ if (validate_runtime_directory(handle, runtime_path, pw->pw_uid)) {
+ r = update_environment(handle, "XDG_RUNTIME_DIR", runtime_path);
+ if (r != PAM_SUCCESS)
+ return r;
}
-
- r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
- if (r != PAM_SUCCESS)
- return r;
}
- if (!isempty(seat)) {
- r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
- if (r != PAM_SUCCESS) {
- pam_syslog(handle, LOG_ERR, "Failed to set seat.");
- return r;
- }
- }
+ /* Most likely we got the session/type/class from environment variables, but might have gotten the data
+ * somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this
+ * data is inherited into the session processes, and programs can rely on them to be initialized. */
+
+ r = update_environment(handle, "XDG_SESSION_TYPE", type);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = update_environment(handle, "XDG_SESSION_CLASS", class);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = update_environment(handle, "XDG_SESSION_DESKTOP", desktop);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ r = update_environment(handle, "XDG_SEAT", seat);
+ if (r != PAM_SUCCESS)
+ return r;
if (vtnr > 0) {
char buf[DECIMAL_STR_MAX(vtnr)];
sprintf(buf, "%u", vtnr);
- r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
- if (r != PAM_SUCCESS) {
- pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
+ r = update_environment(handle, "XDG_VTNR", buf);
+ if (r != PAM_SUCCESS)
return r;
- }
}
r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
@@ -628,7 +683,7 @@ _public_ PAM_EXTERN int pam_sm_close_session(
/* Only release session if it wasn't pre-existing when we
* tried to create it */
- pam_get_data(handle, "systemd.existing", &existing);
+ (void) pam_get_data(handle, "systemd.existing", &existing);
id = pam_getenv(handle, "XDG_SESSION_ID");
if (id && !existing) {