]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/login/pam_systemd.c
Merge pull request #12753 from jrouleau/fix/hibernate-resume-timeout
[thirdparty/systemd.git] / src / login / pam_systemd.c
index 8464df122c403f4373bce752ba652372f80380f5..cd070329f4c409a6ac6fec2353005b2b379f068c 100644 (file)
 #include <security/pam_modules.h>
 #include <security/pam_modutil.h>
 #include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "alloc-util.h"
 #include "audit-util.h"
 #include "bus-common-errors.h"
 #include "bus-error.h"
+#include "bus-internal.h"
 #include "bus-util.h"
 #include "cgroup-util.h"
-#include "def.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-util.h"
@@ -28,9 +31,9 @@
 #include "path-util.h"
 #include "process-util.h"
 #include "socket-util.h"
+#include "stdio-util.h"
 #include "strv.h"
 #include "terminal-util.h"
-#include "util.h"
 
 static int parse_argv(
                 pam_handle_t *handle,
@@ -114,6 +117,15 @@ static int get_user_data(
         return PAM_SUCCESS;
 }
 
+static bool display_is_local(const char *display) {
+        assert(display);
+
+        return
+                display[0] == ':' &&
+                display[1] >= '0' &&
+                display[1] <= '9';
+}
+
 static int socket_from_display(const char *display, char **path) {
         size_t k;
         char *f, *c;
@@ -140,13 +152,11 @@ static int socket_from_display(const char *display, char **path) {
 }
 
 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);
@@ -160,13 +170,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);
@@ -190,6 +202,45 @@ 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) {
+
+        const char *s;
+        _cleanup_free_ char *t = NULL;
+        int r = PAM_BUF_ERR;
+
+        /* We need to export $DBUS_SESSION_BUS_ADDRESS because various applications will not connect
+         * correctly to the bus without it. This setting matches what dbus.socket does for the user
+         * session using 'systemctl --user set-environment'. We want to have the same configuration
+         * in processes started from the PAM session.
+         *
+         * The setting of the address is guarded by the access() check because it is also possible to compile
+         * dbus without --enable-user-session, in which case this socket is not used, and
+         * $DBUS_SESSION_BUS_ADDRESS should not be set. An alternative approach would to not do the access()
+         * check here, and let applications try on their own, by using "unix:path=%s/bus;autolaunch:". But we
+         * expect the socket to be present by the time we do this check, so we can just as well check once
+         * here. */
+
+        s = strjoina(runtime, "/bus");
+        if (access(s, F_OK) < 0)
+                return PAM_SUCCESS;
+
+        if (asprintf(&t, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
+                goto error;
+
+        r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", t, 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;
@@ -220,7 +271,7 @@ static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, co
                                         return r;
                                 }
                         } else
-                                pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.limit: %s, ignoring.", limit);
+                                pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max: %s, ignoring.", limit);
                 }
         }
 
@@ -243,7 +294,7 @@ static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, con
                         return r;
                 }
         } else
-                pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.limit: %s, ignoring.", limit);
+                pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max: %s, ignoring.", limit);
 
         return 0;
 }
@@ -276,14 +327,21 @@ static const char* getenv_harder(pam_handle_t *handle, const char *key, const ch
         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. */
+        /* Looks for an environment variable, preferably in the environment block associated with the
+         * specified PAM handle, falling back to the process' block instead. Why check both? Because we want
+         * to permit configuration of session properties from unit files that invoke PAM services, so that
+         * PAM services don't have to be reworked to set systemd-specific properties, but these properties
+         * can still be set from the unit file Environment= block. */
 
         v = pam_getenv(handle, key);
         if (!isempty(v))
                 return v;
 
-        v = getenv(key);
+        /* We use secure_getenv() here, since we might get loaded into su/sudo, which are SUID. Ideally
+         * they'd clean up the environment before invoking foreign code (such as PAM modules), but alas they
+         * currently don't (to be precise, they clean up the environment they pass to their children, but
+         * not their own environ[]). */
+        v = secure_getenv(key);
         if (!isempty(v))
                 return v;
 
@@ -308,6 +366,36 @@ static int update_environment(pam_handle_t *handle, const char *key, const char
         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,
@@ -362,16 +450,20 @@ _public_ PAM_EXTERN int pam_sm_open_session(
 
         pam_get_item(handle, PAM_SERVICE, (const void**) &service);
         if (streq_ptr(service, "systemd-user")) {
-                _cleanup_free_ char *rt = NULL;
+                char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
 
-                if (asprintf(&rt, "/run/user/"UID_FMT, pw->pw_uid) < 0)
-                        return PAM_BUF_ERR;
+                xsprintf(rt, "/run/user/"UID_FMT, pw->pw_uid);
+                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 = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
-                if (r != PAM_SUCCESS) {
-                        pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
+                r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
+                if (r != PAM_SUCCESS)
                         return r;
-                }
 
                 return PAM_SUCCESS;
         }
@@ -434,7 +526,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         }
 
         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;
         }
 
@@ -535,7 +628,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, "Not creating 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));
@@ -574,7 +668,13 @@ _public_ PAM_EXTERN int pam_sm_open_session(
                  * in privileged apps clobbering the runtime directory
                  * unnecessarily. */
 
-                r = update_environment(handle, "XDG_RUNTIME_DIR", runtime_path);
+                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;
         }