Systemd/src/login/logind-core.c
Lennart Poettering b062ca616c logind: avoid shadow lookups when doing userdb client side
Let's not trigger MACs needlessly.

Ideally everybody would turn on userdb, but if people insist in not
doing so, then let's not attempt to open shadow.

It's a bit ugly to implement this, since shadow information is more than
just passwords (but accound validity metadata), and thus userdb's own
"privieleged" scheme is orthogonal to this, but let's still do this for
the client side.

Fixes: #15105
2020-04-09 14:38:14 +02:00

819 lines
23 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/vt.h>
#if ENABLE_UTMP
#include <utmpx.h>
#endif
#include "sd-device.h"
#include "alloc-util.h"
#include "bus-error.h"
#include "bus-util.h"
#include "cgroup-util.h"
#include "conf-parser.h"
#include "device-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "limits-util.h"
#include "logind.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "udev-util.h"
#include "user-util.h"
#include "userdb.h"
void manager_reset_config(Manager *m) {
assert(m);
m->n_autovts = 6;
m->reserve_vt = 6;
m->remove_ipc = true;
m->inhibit_delay_max = 5 * USEC_PER_SEC;
m->user_stop_delay = 10 * USEC_PER_SEC;
m->handle_power_key = HANDLE_POWEROFF;
m->handle_suspend_key = HANDLE_SUSPEND;
m->handle_hibernate_key = HANDLE_HIBERNATE;
m->handle_lid_switch = HANDLE_SUSPEND;
m->handle_lid_switch_ep = _HANDLE_ACTION_INVALID;
m->handle_lid_switch_docked = HANDLE_IGNORE;
m->power_key_ignore_inhibited = false;
m->suspend_key_ignore_inhibited = false;
m->hibernate_key_ignore_inhibited = false;
m->lid_switch_ignore_inhibited = true;
m->holdoff_timeout_usec = 30 * USEC_PER_SEC;
m->idle_action_usec = 30 * USEC_PER_MINUTE;
m->idle_action = HANDLE_IGNORE;
m->runtime_dir_size = physical_memory_scale(10U, 100U); /* 10% */
m->sessions_max = 8192;
m->inhibitors_max = 8192;
m->kill_user_processes = KILL_USER_PROCESSES;
m->kill_only_users = strv_free(m->kill_only_users);
m->kill_exclude_users = strv_free(m->kill_exclude_users);
}
int manager_parse_config_file(Manager *m) {
assert(m);
return config_parse_many_nulstr(PKGSYSCONFDIR "/logind.conf",
CONF_PATHS_NULSTR("systemd/logind.conf.d"),
"Login\0",
config_item_perf_lookup, logind_gperf_lookup,
CONFIG_PARSE_WARN, m);
}
int manager_add_device(Manager *m, const char *sysfs, bool master, Device **ret_device) {
Device *d;
assert(m);
assert(sysfs);
d = hashmap_get(m->devices, sysfs);
if (d)
/* we support adding master-flags, but not removing them */
d->master = d->master || master;
else {
d = device_new(m, sysfs, master);
if (!d)
return -ENOMEM;
}
if (ret_device)
*ret_device = d;
return 0;
}
int manager_add_seat(Manager *m, const char *id, Seat **ret_seat) {
Seat *s;
int r;
assert(m);
assert(id);
s = hashmap_get(m->seats, id);
if (!s) {
r = seat_new(&s, m, id);
if (r < 0)
return r;
}
if (ret_seat)
*ret_seat = s;
return 0;
}
int manager_add_session(Manager *m, const char *id, Session **ret_session) {
Session *s;
int r;
assert(m);
assert(id);
s = hashmap_get(m->sessions, id);
if (!s) {
r = session_new(&s, m, id);
if (r < 0)
return r;
}
if (ret_session)
*ret_session = s;
return 0;
}
int manager_add_user(
Manager *m,
UserRecord *ur,
User **ret_user) {
User *u;
int r;
assert(m);
assert(ur);
u = hashmap_get(m->users, UID_TO_PTR(ur->uid));
if (!u) {
r = user_new(&u, m, ur);
if (r < 0)
return r;
}
if (ret_user)
*ret_user = u;
return 0;
}
int manager_add_user_by_name(
Manager *m,
const char *name,
User **ret_user) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
int r;
assert(m);
assert(name);
r = userdb_by_name(name, USERDB_AVOID_SHADOW, &ur);
if (r < 0)
return r;
return manager_add_user(m, ur, ret_user);
}
int manager_add_user_by_uid(
Manager *m,
uid_t uid,
User **ret_user) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
int r;
assert(m);
assert(uid_is_valid(uid));
r = userdb_by_uid(uid, USERDB_AVOID_SHADOW, &ur);
if (r < 0)
return r;
return manager_add_user(m, ur, ret_user);
}
int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **ret) {
Inhibitor *i;
int r;
assert(m);
assert(id);
i = hashmap_get(m->inhibitors, id);
if (!i) {
r = inhibitor_new(&i, m, id);
if (r < 0)
return r;
}
if (ret)
*ret = i;
return 0;
}
int manager_add_button(Manager *m, const char *name, Button **ret_button) {
Button *b;
assert(m);
assert(name);
b = hashmap_get(m->buttons, name);
if (!b) {
b = button_new(m, name);
if (!b)
return -ENOMEM;
}
if (ret_button)
*ret_button = b;
return 0;
}
int manager_process_seat_device(Manager *m, sd_device *d) {
Device *device;
int r;
assert(m);
if (device_for_action(d, DEVICE_ACTION_REMOVE)) {
const char *syspath;
r = sd_device_get_syspath(d, &syspath);
if (r < 0)
return 0;
device = hashmap_get(m->devices, syspath);
if (!device)
return 0;
seat_add_to_gc_queue(device->seat);
device_free(device);
} else {
const char *sn, *syspath;
bool master;
Seat *seat;
if (sd_device_get_property_value(d, "ID_SEAT", &sn) < 0 || isempty(sn))
sn = "seat0";
if (!seat_name_is_valid(sn)) {
log_device_warning(d, "Device with invalid seat name %s found, ignoring.", sn);
return 0;
}
seat = hashmap_get(m->seats, sn);
master = sd_device_has_tag(d, "master-of-seat") > 0;
/* Ignore non-master devices for unknown seats */
if (!master && !seat)
return 0;
r = sd_device_get_syspath(d, &syspath);
if (r < 0)
return r;
r = manager_add_device(m, syspath, master, &device);
if (r < 0)
return r;
if (!seat) {
r = manager_add_seat(m, sn, &seat);
if (r < 0) {
if (!device->seat)
device_free(device);
return r;
}
}
device_attach(device, seat);
seat_start(seat);
}
return 0;
}
int manager_process_button_device(Manager *m, sd_device *d) {
const char *sysname;
Button *b;
int r;
assert(m);
r = sd_device_get_sysname(d, &sysname);
if (r < 0)
return r;
if (device_for_action(d, DEVICE_ACTION_REMOVE)) {
b = hashmap_get(m->buttons, sysname);
if (!b)
return 0;
button_free(b);
} else {
const char *sn;
r = manager_add_button(m, sysname, &b);
if (r < 0)
return r;
if (sd_device_get_property_value(d, "ID_SEAT", &sn) < 0 || isempty(sn))
sn = "seat0";
button_set_seat(b, sn);
r = button_open(b);
if (r < 0) /* event device doesn't have any keys or switches relevant to us? (or any other error
* opening the device?) let's close the button again. */
button_free(b);
}
return 0;
}
int manager_get_session_by_pid(Manager *m, pid_t pid, Session **ret) {
_cleanup_free_ char *unit = NULL;
Session *s;
int r;
assert(m);
if (!pid_is_valid(pid))
return -EINVAL;
s = hashmap_get(m->sessions_by_leader, PID_TO_PTR(pid));
if (!s) {
r = cg_pid_get_unit(pid, &unit);
if (r >= 0)
s = hashmap_get(m->session_units, unit);
}
if (ret)
*ret = s;
return !!s;
}
int manager_get_user_by_pid(Manager *m, pid_t pid, User **ret) {
_cleanup_free_ char *unit = NULL;
User *u = NULL;
int r;
assert(m);
if (!pid_is_valid(pid))
return -EINVAL;
r = cg_pid_get_slice(pid, &unit);
if (r >= 0)
u = hashmap_get(m->user_units, unit);
if (ret)
*ret = u;
return !!u;
}
int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
Session *s;
bool idle_hint;
dual_timestamp ts = DUAL_TIMESTAMP_NULL;
Iterator i;
assert(m);
idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t, false, false, 0, NULL);
HASHMAP_FOREACH(s, m->sessions, i) {
dual_timestamp k;
int ih;
ih = session_get_idle_hint(s, &k);
if (ih < 0)
return ih;
if (!ih) {
if (!idle_hint) {
if (k.monotonic < ts.monotonic)
ts = k;
} else {
idle_hint = false;
ts = k;
}
} else if (idle_hint) {
if (k.monotonic > ts.monotonic)
ts = k;
}
}
if (t)
*t = ts;
return idle_hint;
}
bool manager_shall_kill(Manager *m, const char *user) {
assert(m);
assert(user);
if (!m->kill_exclude_users && streq(user, "root"))
return false;
if (strv_contains(m->kill_exclude_users, user))
return false;
if (!strv_isempty(m->kill_only_users))
return strv_contains(m->kill_only_users, user);
return m->kill_user_processes;
}
int config_parse_n_autovts(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
unsigned *n = data;
unsigned o;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
r = safe_atou(rvalue, &o);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse number of autovts, ignoring: %s", rvalue);
return 0;
}
if (o > 15) {
log_syntax(unit, LOG_ERR, filename, line, r, "A maximum of 15 autovts are supported, ignoring: %s", rvalue);
return 0;
}
*n = o;
return 0;
}
static int vt_is_busy(unsigned vtnr) {
struct vt_stat vt_stat;
int r = 0;
_cleanup_close_ int fd;
assert(vtnr >= 1);
/* VT_GETSTATE "cannot return state for more than 16 VTs, since v_state is short" */
assert(vtnr <= 15);
/* We explicitly open /dev/tty1 here instead of /dev/tty0. If
* we'd open the latter we'd open the foreground tty which
* hence would be unconditionally busy. By opening /dev/tty1
* we avoid this. Since tty1 is special and needs to be an
* explicitly loaded getty or DM this is safe. */
fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return -errno;
if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0)
r = -errno;
else
r = !!(vt_stat.v_state & (1 << vtnr));
return r;
}
int manager_spawn_autovt(Manager *m, unsigned vtnr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
char name[sizeof("autovt@tty.service") + DECIMAL_STR_MAX(unsigned)];
int r;
assert(m);
assert(vtnr >= 1);
if (vtnr > m->n_autovts &&
vtnr != m->reserve_vt)
return 0;
if (vtnr != m->reserve_vt) {
/* If this is the reserved TTY, we'll start the getty
* on it in any case, but otherwise only if it is not
* busy. */
r = vt_is_busy(vtnr);
if (r < 0)
return r;
else if (r > 0)
return -EBUSY;
}
snprintf(name, sizeof(name), "autovt@tty%u.service", vtnr);
r = sd_bus_call_method(
m->bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"StartUnit",
&error,
NULL,
"ss", name, "fail");
if (r < 0)
return log_error_errno(r, "Failed to start %s: %s", name, bus_error_message(&error, r));
return 0;
}
bool manager_is_lid_closed(Manager *m) {
Iterator i;
Button *b;
HASHMAP_FOREACH(b, m->buttons, i)
if (b->lid_closed)
return true;
return false;
}
static bool manager_is_docked(Manager *m) {
Iterator i;
Button *b;
HASHMAP_FOREACH(b, m->buttons, i)
if (b->docked)
return true;
return false;
}
static int manager_count_external_displays(Manager *m) {
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
sd_device *d;
int r, n = 0;
r = sd_device_enumerator_new(&e);
if (r < 0)
return r;
r = sd_device_enumerator_allow_uninitialized(e);
if (r < 0)
return r;
r = sd_device_enumerator_add_match_subsystem(e, "drm", true);
if (r < 0)
return r;
FOREACH_DEVICE(e, d) {
const char *status, *enabled, *dash, *nn, *subsys;
sd_device *p;
if (sd_device_get_parent(d, &p) < 0)
continue;
/* If the parent shares the same subsystem as the
* device we are looking at then it is a connector,
* which is what we are interested in. */
if (sd_device_get_subsystem(p, &subsys) < 0 || !streq(subsys, "drm"))
continue;
if (sd_device_get_sysname(d, &nn) < 0)
continue;
/* Ignore internal displays: the type is encoded in the sysfs name, as the second dash separated item
* (the first is the card name, the last the connector number). We implement a blacklist of external
* displays here, rather than a whitelist of internal ones, to ensure we don't block suspends too
* eagerly. */
dash = strchr(nn, '-');
if (!dash)
continue;
dash++;
if (!STARTSWITH_SET(dash,
"VGA-", "DVI-I-", "DVI-D-", "DVI-A-"
"Composite-", "SVIDEO-", "Component-",
"DIN-", "DP-", "HDMI-A-", "HDMI-B-", "TV-"))
continue;
/* Ignore ports that are not enabled */
if (sd_device_get_sysattr_value(d, "enabled", &enabled) < 0 || !streq(enabled, "enabled"))
continue;
/* We count any connector which is not explicitly
* "disconnected" as connected. */
if (sd_device_get_sysattr_value(d, "status", &status) < 0 || !streq(status, "disconnected"))
n++;
}
return n;
}
bool manager_is_docked_or_external_displays(Manager *m) {
int n;
/* If we are docked don't react to lid closing */
if (manager_is_docked(m)) {
log_debug("System is docked.");
return true;
}
/* If we have more than one display connected,
* assume that we are docked. */
n = manager_count_external_displays(m);
if (n < 0)
log_warning_errno(n, "Display counting failed: %m");
else if (n >= 1) {
log_debug("External (%i) displays connected.", n);
return true;
}
return false;
}
bool manager_is_on_external_power(void) {
int r;
/* For now we only check for AC power, but 'external power' can apply to anything that isn't an internal
* battery */
r = on_ac_power();
if (r < 0)
log_warning_errno(r, "Failed to read AC power status: %m");
return r != 0; /* Treat failure as 'on AC' */
}
bool manager_all_buttons_ignored(Manager *m) {
assert(m);
if (m->handle_power_key != HANDLE_IGNORE)
return false;
if (m->handle_suspend_key != HANDLE_IGNORE)
return false;
if (m->handle_hibernate_key != HANDLE_IGNORE)
return false;
if (m->handle_lid_switch != HANDLE_IGNORE)
return false;
if (!IN_SET(m->handle_lid_switch_ep, _HANDLE_ACTION_INVALID, HANDLE_IGNORE))
return false;
if (m->handle_lid_switch_docked != HANDLE_IGNORE)
return false;
return true;
}
int manager_read_utmp(Manager *m) {
#if ENABLE_UTMP
int r;
assert(m);
if (utmpxname(_PATH_UTMPX) < 0)
return log_error_errno(errno, "Failed to set utmp path to " _PATH_UTMPX ": %m");
setutxent();
for (;;) {
_cleanup_free_ char *t = NULL;
struct utmpx *u;
const char *c;
Session *s;
errno = 0;
u = getutxent();
if (!u) {
if (errno != 0)
log_warning_errno(errno, "Failed to read " _PATH_UTMPX ", ignoring: %m");
r = 0;
break;
}
if (u->ut_type != USER_PROCESS)
continue;
if (!pid_is_valid(u->ut_pid))
continue;
t = strndup(u->ut_line, sizeof(u->ut_line));
if (!t) {
r = log_oom();
break;
}
c = path_startswith(t, "/dev/");
if (c) {
r = free_and_strdup(&t, c);
if (r < 0) {
log_oom();
break;
}
}
if (isempty(t))
continue;
s = hashmap_get(m->sessions_by_leader, PID_TO_PTR(u->ut_pid));
if (!s)
continue;
if (s->tty_validity == TTY_FROM_UTMP && !streq_ptr(s->tty, t)) {
/* This may happen on multiplexed SSH connection (i.e. 'SSH connection sharing'). In
* this case PAM and utmp sessions don't match. In such a case let's invalidate the TTY
* information and never acquire it again. */
s->tty = mfree(s->tty);
s->tty_validity = TTY_UTMP_INCONSISTENT;
log_debug("Session '%s' has inconsistent TTY information, dropping TTY information.", s->id);
continue;
}
/* Never override what we figured out once */
if (s->tty || s->tty_validity >= 0)
continue;
s->tty = TAKE_PTR(t);
s->tty_validity = TTY_FROM_UTMP;
log_debug("Acquired TTY information '%s' from utmp for session '%s'.", s->tty, s->id);
}
endutxent();
return r;
#else
return 0;
#endif
}
#if ENABLE_UTMP
static int manager_dispatch_utmp(sd_event_source *s, const struct inotify_event *event, void *userdata) {
Manager *m = userdata;
assert(m);
/* If there's indication the file itself might have been removed or became otherwise unavailable, then let's
* reestablish the watch on whatever there's now. */
if ((event->mask & (IN_ATTRIB|IN_DELETE_SELF|IN_MOVE_SELF|IN_Q_OVERFLOW|IN_UNMOUNT)) != 0)
manager_connect_utmp(m);
(void) manager_read_utmp(m);
return 0;
}
#endif
void manager_connect_utmp(Manager *m) {
#if ENABLE_UTMP
sd_event_source *s = NULL;
int r;
assert(m);
/* Watch utmp for changes via inotify. We do this to deal with tools such as ssh, which will register the PAM
* session early, and acquire a TTY only much later for the connection. Thus during PAM the TTY won't be known
* yet. ssh will register itself with utmp when it finally acquired the TTY. Hence, let's make use of this, and
* watch utmp for the TTY asynchronously. We use the PAM session's leader PID as key, to find the right entry.
*
* Yes, relying on utmp is pretty ugly, but it's good enough for informational purposes, as well as idle
* detection (which, for tty sessions, relies on the TTY used) */
r = sd_event_add_inotify(m->event, &s, _PATH_UTMPX, IN_MODIFY|IN_MOVE_SELF|IN_DELETE_SELF|IN_ATTRIB, manager_dispatch_utmp, m);
if (r < 0)
log_full_errno(r == -ENOENT ? LOG_DEBUG: LOG_WARNING, r, "Failed to create inotify watch on " _PATH_UTMPX ", ignoring: %m");
else {
r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE);
if (r < 0)
log_warning_errno(r, "Failed to adjust utmp event source priority, ignoring: %m");
(void) sd_event_source_set_description(s, "utmp");
}
sd_event_source_unref(m->utmp_event_source);
m->utmp_event_source = s;
#endif
}
void manager_reconnect_utmp(Manager *m) {
#if ENABLE_UTMP
assert(m);
if (m->utmp_event_source)
return;
manager_connect_utmp(m);
#endif
}