86b31d
From d897789b4dc7d115c915842eabf33ed3de20110a Mon Sep 17 00:00:00 2001
86b31d
From: Lennart Poettering <lennart@poettering.net>
86b31d
Date: Tue, 7 Aug 2018 13:49:34 +0200
86b31d
Subject: [PATCH] logind: optionally watch utmp for login data
86b31d
86b31d
This allows us to determine the TTY an ssh session is for, which is
86b31d
useful to to proper idle detection for ssh sessions.
86b31d
86b31d
Fixes: #9622
86b31d
(cherry picked from commit 3d0ef5c7e00155bc74f6f71c34cad518a4ff56ba)
86b31d
86b31d
Related: #2122288
86b31d
---
86b31d
 src/login/logind-core.c    | 143 +++++++++++++++++++++++++++++++++++++
86b31d
 src/login/logind-dbus.c    |   5 ++
86b31d
 src/login/logind-session.c |  24 +++++++
86b31d
 src/login/logind-session.h |  14 +++-
86b31d
 src/login/logind.c         |  10 +++
86b31d
 src/login/logind.h         |   8 +++
86b31d
 6 files changed, 203 insertions(+), 1 deletion(-)
86b31d
86b31d
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
86b31d
index 0ed812a2c8..7e33f8e6aa 100644
86b31d
--- a/src/login/logind-core.c
86b31d
+++ b/src/login/logind-core.c
86b31d
@@ -5,6 +5,9 @@
86b31d
 #include <sys/ioctl.h>
86b31d
 #include <sys/types.h>
86b31d
 #include <linux/vt.h>
86b31d
+#if ENABLE_UTMP
86b31d
+#include <utmpx.h>
86b31d
+#endif
86b31d
 
86b31d
 #include "alloc-util.h"
86b31d
 #include "bus-error.h"
86b31d
@@ -14,6 +17,7 @@
86b31d
 #include "fd-util.h"
86b31d
 #include "logind.h"
86b31d
 #include "parse-util.h"
86b31d
+#include "path-util.h"
86b31d
 #include "process-util.h"
86b31d
 #include "strv.h"
86b31d
 #include "terminal-util.h"
86b31d
@@ -692,3 +696,142 @@ bool manager_all_buttons_ignored(Manager *m) {
86b31d
 
86b31d
         return true;
86b31d
 }
86b31d
+
86b31d
+int manager_read_utmp(Manager *m) {
86b31d
+#if ENABLE_UTMP
86b31d
+        int r;
86b31d
+
86b31d
+        assert(m);
86b31d
+
86b31d
+        if (utmpxname(_PATH_UTMPX) < 0)
86b31d
+                return log_error_errno(errno, "Failed to set utmp path to " _PATH_UTMPX ": %m");
86b31d
+
86b31d
+        setutxent();
86b31d
+
86b31d
+        for (;;) {
86b31d
+                _cleanup_free_ char *t = NULL;
86b31d
+                struct utmpx *u;
86b31d
+                const char *c;
86b31d
+                Session *s;
86b31d
+
86b31d
+                errno = 0;
86b31d
+                u = getutxent();
86b31d
+                if (!u) {
86b31d
+                        if (errno != 0)
86b31d
+                                log_warning_errno(errno, "Failed to read " _PATH_UTMPX ", ignoring: %m");
86b31d
+                        r = 0;
86b31d
+                        break;
86b31d
+                }
86b31d
+
86b31d
+                if (u->ut_type != USER_PROCESS)
86b31d
+                        continue;
86b31d
+
86b31d
+                if (!pid_is_valid(u->ut_pid))
86b31d
+                        continue;
86b31d
+
86b31d
+                t = strndup(u->ut_line, sizeof(u->ut_line));
86b31d
+                if (!t) {
86b31d
+                        r = log_oom();
86b31d
+                        break;
86b31d
+                }
86b31d
+
86b31d
+                c = path_startswith(t, "/dev/");
86b31d
+                if (c) {
86b31d
+                        r = free_and_strdup(&t, c);
86b31d
+                        if (r < 0) {
86b31d
+                                log_oom();
86b31d
+                                break;
86b31d
+                        }
86b31d
+                }
86b31d
+
86b31d
+                if (isempty(t))
86b31d
+                        continue;
86b31d
+
86b31d
+                s = hashmap_get(m->sessions_by_leader, PID_TO_PTR(u->ut_pid));
86b31d
+                if (!s)
86b31d
+                        continue;
86b31d
+
86b31d
+                if (s->tty_validity == TTY_FROM_UTMP && !streq_ptr(s->tty, t)) {
86b31d
+                        /* This may happen on multiplexed SSH connection (i.e. 'SSH connection sharing'). In
86b31d
+                         * this case PAM and utmp sessions don't match. In such a case let's invalidate the TTY
86b31d
+                         * information and never acquire it again. */
86b31d
+
86b31d
+                        s->tty = mfree(s->tty);
86b31d
+                        s->tty_validity = TTY_UTMP_INCONSISTENT;
86b31d
+                        log_debug("Session '%s' has inconsistent TTY information, dropping TTY information.", s->id);
86b31d
+                        continue;
86b31d
+                }
86b31d
+
86b31d
+                /* Never override what we figured out once */
86b31d
+                if (s->tty || s->tty_validity >= 0)
86b31d
+                        continue;
86b31d
+
86b31d
+                s->tty = TAKE_PTR(t);
86b31d
+                s->tty_validity = TTY_FROM_UTMP;
86b31d
+                log_debug("Acquired TTY information '%s' from utmp for session '%s'.", s->tty, s->id);
86b31d
+        }
86b31d
+
86b31d
+        endutxent();
86b31d
+        return r;
86b31d
+#else
86b31d
+        return 0
86b31d
+#endif
86b31d
+}
86b31d
+
86b31d
+#if ENABLE_UTMP
86b31d
+static int manager_dispatch_utmp(sd_event_source *s, const struct inotify_event *event, void *userdata) {
86b31d
+        Manager *m = userdata;
86b31d
+
86b31d
+        assert(m);
86b31d
+
86b31d
+        /* If there's indication the file itself might have been removed or became otherwise unavailable, then let's
86b31d
+         * reestablish the watch on whatever there's now. */
86b31d
+        if ((event->mask & (IN_ATTRIB|IN_DELETE_SELF|IN_MOVE_SELF|IN_Q_OVERFLOW|IN_UNMOUNT)) != 0)
86b31d
+                manager_connect_utmp(m);
86b31d
+
86b31d
+        (void) manager_read_utmp(m);
86b31d
+        return 0;
86b31d
+}
86b31d
+#endif
86b31d
+
86b31d
+void manager_connect_utmp(Manager *m) {
86b31d
+#if ENABLE_UTMP
86b31d
+        sd_event_source *s = NULL;
86b31d
+        int r;
86b31d
+
86b31d
+        assert(m);
86b31d
+
86b31d
+        /* Watch utmp for changes via inotify. We do this to deal with tools such as ssh, which will register the PAM
86b31d
+         * session early, and acquire a TTY only much later for the connection. Thus during PAM the TTY won't be known
86b31d
+         * yet. ssh will register itself with utmp when it finally acquired the TTY. Hence, let's make use of this, and
86b31d
+         * watch utmp for the TTY asynchronously. We use the PAM session's leader PID as key, to find the right entry.
86b31d
+         *
86b31d
+         * Yes, relying on utmp is pretty ugly, but it's good enough for informational purposes, as well as idle
86b31d
+         * detection (which, for tty sessions, relies on the TTY used) */
86b31d
+
86b31d
+        r = sd_event_add_inotify(m->event, &s, _PATH_UTMPX, IN_MODIFY|IN_MOVE_SELF|IN_DELETE_SELF|IN_ATTRIB, manager_dispatch_utmp, m);
86b31d
+        if (r < 0)
86b31d
+                log_full_errno(r == -ENOENT ? LOG_DEBUG: LOG_WARNING, r, "Failed to create inotify watch on " _PATH_UTMPX ", ignoring: %m");
86b31d
+        else {
86b31d
+                r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE);
86b31d
+                if (r < 0)
86b31d
+                        log_warning_errno(r, "Failed to adjust utmp event source priority, ignoring: %m");
86b31d
+
86b31d
+                (void) sd_event_source_set_description(s, "utmp");
86b31d
+        }
86b31d
+
86b31d
+        sd_event_source_unref(m->utmp_event_source);
86b31d
+        m->utmp_event_source = s;
86b31d
+#endif
86b31d
+}
86b31d
+
86b31d
+void manager_reconnect_utmp(Manager *m) {
86b31d
+#if ENABLE_UTMP
86b31d
+        assert(m);
86b31d
+
86b31d
+        if (m->utmp_event_source)
86b31d
+                return;
86b31d
+
86b31d
+        manager_connect_utmp(m);
86b31d
+#endif
86b31d
+}
86b31d
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
86b31d
index 1bb152bc20..0248042308 100644
86b31d
--- a/src/login/logind-dbus.c
86b31d
+++ b/src/login/logind-dbus.c
86b31d
@@ -772,6 +772,9 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
86b31d
                 } while (hashmap_get(m->sessions, id));
86b31d
         }
86b31d
 
86b31d
+        /* If we are not watching utmp aleady, try again */
86b31d
+        manager_reconnect_utmp(m);
86b31d
+
86b31d
         r = manager_add_user_by_uid(m, uid, &user);
86b31d
         if (r < 0)
86b31d
                 goto fail;
86b31d
@@ -795,6 +798,8 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus
86b31d
                         r = -ENOMEM;
86b31d
                         goto fail;
86b31d
                 }
86b31d
+
86b31d
+                session->tty_validity = TTY_FROM_PAM;
86b31d
         }
86b31d
 
86b31d
         if (!isempty(display)) {
86b31d
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
86b31d
index 1143a834a4..d666f86d3f 100644
86b31d
--- a/src/login/logind-session.c
86b31d
+++ b/src/login/logind-session.c
86b31d
@@ -56,6 +56,7 @@ int session_new(Session **ret, Manager *m, const char *id) {
86b31d
                 .fifo_fd = -1,
86b31d
                 .vtfd = -1,
86b31d
                 .audit_id = AUDIT_SESSION_INVALID,
86b31d
+                .tty_validity = _TTY_VALIDITY_INVALID,
86b31d
         };
86b31d
 
86b31d
         s->state_file = strappend("/run/systemd/sessions/", id);
86b31d
@@ -219,6 +220,9 @@ int session_save(Session *s) {
86b31d
         if (s->tty)
86b31d
                 fprintf(f, "TTY=%s\n", s->tty);
86b31d
 
86b31d
+        if (s->tty_validity >= 0)
86b31d
+                fprintf(f, "TTY_VALIDITY=%s\n", tty_validity_to_string(s->tty_validity));
86b31d
+
86b31d
         if (s->display)
86b31d
                 fprintf(f, "DISPLAY=%s\n", s->display);
86b31d
 
86b31d
@@ -355,6 +359,7 @@ static int session_load_devices(Session *s, const char *devices) {
86b31d
 int session_load(Session *s) {
86b31d
         _cleanup_free_ char *remote = NULL,
86b31d
                 *seat = NULL,
86b31d
+                *tty_validity = NULL,
86b31d
                 *vtnr = NULL,
86b31d
                 *state = NULL,
86b31d
                 *position = NULL,
86b31d
@@ -380,6 +385,7 @@ int session_load(Session *s) {
86b31d
                            "FIFO",           &s->fifo_path,
86b31d
                            "SEAT",           &seat,
86b31d
                            "TTY",            &s->tty,
86b31d
+                           "TTY_VALIDITY",   &tty_validity,
86b31d
                            "DISPLAY",        &s->display,
86b31d
                            "REMOTE_HOST",    &s->remote_host,
86b31d
                            "REMOTE_USER",    &s->remote_user,
86b31d
@@ -456,6 +462,16 @@ int session_load(Session *s) {
86b31d
                 seat_claim_position(s->seat, s, npos);
86b31d
         }
86b31d
 
86b31d
+        if (tty_validity) {
86b31d
+                TTYValidity v;
86b31d
+
86b31d
+                v = tty_validity_from_string(tty_validity);
86b31d
+                if (v < 0)
86b31d
+                        log_debug("Failed to parse TTY validity: %s", tty_validity);
86b31d
+                else
86b31d
+                        s->tty_validity = v;
86b31d
+        }
86b31d
+
86b31d
         if (leader) {
86b31d
                 if (parse_pid(leader, &s->leader) >= 0)
86b31d
                         (void) audit_session_from_pid(s->leader, &s->audit_id);
86b31d
@@ -1368,3 +1384,11 @@ static const char* const kill_who_table[_KILL_WHO_MAX] = {
86b31d
 };
86b31d
 
86b31d
 DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho);
86b31d
+
86b31d
+static const char* const tty_validity_table[_TTY_VALIDITY_MAX] = {
86b31d
+        [TTY_FROM_PAM] = "from-pam",
86b31d
+        [TTY_FROM_UTMP] = "from-utmp",
86b31d
+        [TTY_UTMP_INCONSISTENT] = "utmp-inconsistent",
86b31d
+};
86b31d
+
86b31d
+DEFINE_STRING_TABLE_LOOKUP(tty_validity, TTYValidity);
86b31d
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
86b31d
index 9bd0c96a03..7da845cea3 100644
86b31d
--- a/src/login/logind-session.h
86b31d
+++ b/src/login/logind-session.h
86b31d
@@ -46,6 +46,14 @@ enum KillWho {
86b31d
         _KILL_WHO_INVALID = -1
86b31d
 };
86b31d
 
86b31d
+typedef enum TTYValidity {
86b31d
+        TTY_FROM_PAM,
86b31d
+        TTY_FROM_UTMP,
86b31d
+        TTY_UTMP_INCONSISTENT, /* may happen on ssh sessions with multiplexed TTYs */
86b31d
+        _TTY_VALIDITY_MAX,
86b31d
+        _TTY_VALIDITY_INVALID = -1,
86b31d
+} TTYValidity;
86b31d
+
86b31d
 struct Session {
86b31d
         Manager *manager;
86b31d
 
86b31d
@@ -60,8 +68,9 @@ struct Session {
86b31d
 
86b31d
         dual_timestamp timestamp;
86b31d
 
86b31d
-        char *tty;
86b31d
         char *display;
86b31d
+        char *tty;
86b31d
+        TTYValidity tty_validity;
86b31d
 
86b31d
         bool remote;
86b31d
         char *remote_user;
86b31d
@@ -159,6 +168,9 @@ SessionClass session_class_from_string(const char *s) _pure_;
86b31d
 const char *kill_who_to_string(KillWho k) _const_;
86b31d
 KillWho kill_who_from_string(const char *s) _pure_;
86b31d
 
86b31d
+const char* tty_validity_to_string(TTYValidity t) _const_;
86b31d
+TTYValidity tty_validity_from_string(const char *s) _pure_;
86b31d
+
86b31d
 int session_prepare_vt(Session *s);
86b31d
 void session_restore_vt(Session *s);
86b31d
 void session_leave_vt(Session *s);
86b31d
diff --git a/src/login/logind.c b/src/login/logind.c
86b31d
index 6c208c8e89..25de9a6ab2 100644
86b31d
--- a/src/login/logind.c
86b31d
+++ b/src/login/logind.c
86b31d
@@ -132,6 +132,10 @@ static Manager* manager_unref(Manager *m) {
86b31d
         sd_event_source_unref(m->udev_button_event_source);
86b31d
         sd_event_source_unref(m->lid_switch_ignore_event_source);
86b31d
 
86b31d
+#if ENABLE_UTMP
86b31d
+        sd_event_source_unref(m->utmp_event_source);
86b31d
+#endif
86b31d
+
86b31d
         safe_close(m->console_active_fd);
86b31d
 
86b31d
         udev_monitor_unref(m->udev_seat_monitor);
86b31d
@@ -1095,6 +1099,9 @@ static int manager_startup(Manager *m) {
86b31d
         if (r < 0)
86b31d
                 return log_error_errno(r, "Failed to register SIGHUP handler: %m");
86b31d
 
86b31d
+        /* Connect to utmp */
86b31d
+        manager_connect_utmp(m);
86b31d
+
86b31d
         /* Connect to console */
86b31d
         r = manager_connect_console(m);
86b31d
         if (r < 0)
86b31d
@@ -1150,6 +1157,9 @@ static int manager_startup(Manager *m) {
86b31d
         /* Reserve the special reserved VT */
86b31d
         manager_reserve_vt(m);
86b31d
 
86b31d
+        /* Read in utmp if it exists */
86b31d
+        manager_read_utmp(m);
86b31d
+
86b31d
         /* And start everything */
86b31d
         HASHMAP_FOREACH(seat, m->seats, i)
86b31d
                 seat_start(seat);
86b31d
diff --git a/src/login/logind.h b/src/login/logind.h
86b31d
index d29b01c75b..bb127bf4a5 100644
86b31d
--- a/src/login/logind.h
86b31d
+++ b/src/login/logind.h
86b31d
@@ -43,6 +43,10 @@ struct Manager {
86b31d
         sd_event_source *udev_vcsa_event_source;
86b31d
         sd_event_source *udev_button_event_source;
86b31d
 
86b31d
+#if ENABLE_UTMP
86b31d
+        sd_event_source *utmp_event_source;
86b31d
+#endif
86b31d
+
86b31d
         int console_active_fd;
86b31d
 
86b31d
         unsigned n_autovts;
86b31d
@@ -150,6 +154,10 @@ bool manager_is_docked_or_external_displays(Manager *m);
86b31d
 bool manager_is_on_external_power(void);
86b31d
 bool manager_all_buttons_ignored(Manager *m);
86b31d
 
86b31d
+int manager_read_utmp(Manager *m);
86b31d
+void manager_connect_utmp(Manager *m);
86b31d
+void manager_reconnect_utmp(Manager *m);
86b31d
+
86b31d
 extern const sd_bus_vtable manager_vtable[];
86b31d
 
86b31d
 int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error);