<varlistentry>
<term><command>show-session</command> <optional><replaceable>ID</replaceable>…</optional></term>
- <listitem><para>Show properties of one or more sessions or the
- manager itself. If no argument is specified, properties of the
- manager will be shown. If a session ID is specified,
- properties of the session are shown. By default, empty
- properties are suppressed. Use <option>--all</option> to show
- those too. To select specific properties to show, use
- <option>--property=</option>. This command is intended to be
- used whenever computer-parsable output is required. Use
- <command>session-status</command> if you are looking for
- formatted human-readable output.</para>
+ <listitem><para>Show properties of one or more sessions or the manager itself. If no argument is
+ specified, properties of the manager will be shown. If a session ID is specified, properties of
+ the session are shown. Specially, if the given ID is <literal>self</literal>, the session to which
+ the <command>loginctl</command> process belongs is used. If <literal>auto</literal>, the current
+ session is used as with <literal>self</literal> if exists, and falls back to the current user's
+ graphical session. By default, empty properties are suppressed. Use <option>--all</option> to show
+ those too. To select specific properties to show, use <option>--property=</option>. This command
+ is intended to be used whenever computer-parsable output is required. Use <command>session-status</command>
+ if you are looking for formatted human-readable output.</para>
<xi:include href="version-info.xml" xpointer="v233"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--kill-whom=</option></term>
- <listitem><para>When used with
- <command>kill-session</command>, choose which processes to
- kill. Must be one of <option>leader</option>, or
- <option>all</option> to select whether to kill only the leader
- process of the session or all processes of the session. If
- omitted, defaults to <option>all</option>.</para>
+ <listitem><para>When used with <command>kill-session</command>, choose which processes to kill.
+ Takes one of <literal>leader</literal> or <literal>all</literal>, to select whether to kill only
+ the leader process of the session or all processes of the session. If omitted, defaults to
+ <option>all</option>.</para>
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>
#define SO_PEERGROUPS 59
#endif
+#ifndef SO_PEERPIDFD
+#define SO_PEERPIDFD 77
+#endif
+
#ifndef SO_BINDTOIFINDEX
#define SO_BINDTOIFINDEX 62
#endif
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/wait.h>
+
+#ifndef P_PIDFD
+#define P_PIDFD 3
+#endif
#include "errno-util.h"
#include "fd-util.h"
#include "missing_syscall.h"
+#include "missing_wait.h"
#include "parse-util.h"
#include "pidref.h"
#include "process-util.h"
return pidref->pid == getpid_cached();
}
+int pidref_wait(const PidRef *pidref, siginfo_t *ret, int options) {
+ int r;
+
+ if (!pidref_is_set(pidref))
+ return -ESRCH;
+
+ if (pidref->pid == 1 || pidref->pid == getpid_cached())
+ return -ECHILD;
+
+ siginfo_t si = {};
+
+ if (pidref->fd >= 0) {
+ r = RET_NERRNO(waitid(P_PIDFD, pidref->fd, &si, options));
+ if (r >= 0) {
+ if (ret)
+ *ret = si;
+ return r;
+ }
+ if (r != -EINVAL) /* P_PIDFD was added in kernel 5.4 only */
+ return r;
+ }
+
+ r = RET_NERRNO(waitid(P_PID, pidref->pid, &si, options));
+ if (r >= 0 && ret)
+ *ret = si;
+ return r;
+}
+
+int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret) {
+ int r;
+
+ for (;;) {
+ r = pidref_wait(pidref, ret, WEXITED);
+ if (r != -EINTR)
+ return r;
+ }
+}
+
static void pidref_hash_func(const PidRef *pidref, struct siphash *state) {
siphash24_compress_typesafe(pidref->pid, state);
}
int pidref_kill(const PidRef *pidref, int sig);
int pidref_kill_and_sigcont(const PidRef *pidref, int sig);
-int pidref_sigqueue(const PidRef *pidfref, int sig, int value);
+int pidref_sigqueue(const PidRef *pidref, int sig, int value);
+
+int pidref_wait(const PidRef *pidref, siginfo_t *siginfo, int options);
+int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret);
+
+static inline void pidref_done_sigkill_wait(PidRef *pidref) {
+ if (!pidref_is_set(pidref))
+ return;
+
+ (void) pidref_kill(pidref, SIGKILL);
+ (void) pidref_wait_for_terminate(pidref, NULL);
+ pidref_done(pidref);
+}
int pidref_verify(const PidRef *pidref);
return 0;
}
+int pid_get_start_time(pid_t pid, uint64_t *ret) {
+ _cleanup_free_ char *line = NULL;
+ const char *p;
+ int r;
+
+ assert(pid >= 0);
+
+ p = procfs_file_alloca(pid, "stat");
+ r = read_one_line_file(p, &line);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0)
+ return r;
+
+ /* Let's skip the pid and comm fields. The latter is enclosed in () but does not escape any () in its
+ * value, so let's skip over it manually */
+
+ p = strrchr(line, ')');
+ if (!p)
+ return -EIO;
+
+ p++;
+
+ unsigned long llu;
+
+ if (sscanf(p, " "
+ "%*c " /* state */
+ "%*u " /* ppid */
+ "%*u " /* pgrp */
+ "%*u " /* session */
+ "%*u " /* tty_nr */
+ "%*u " /* tpgid */
+ "%*u " /* flags */
+ "%*u " /* minflt */
+ "%*u " /* cminflt */
+ "%*u " /* majflt */
+ "%*u " /* cmajflt */
+ "%*u " /* utime */
+ "%*u " /* stime */
+ "%*u " /* cutime */
+ "%*u " /* cstime */
+ "%*i " /* priority */
+ "%*i " /* nice */
+ "%*u " /* num_threads */
+ "%*u " /* itrealvalue */
+ "%lu ", /* starttime */
+ &llu) != 1)
+ return -EIO;
+
+ if (ret)
+ *ret = llu;
+
+ return 0;
+}
+
+int pidref_get_start_time(const PidRef *pid, uint64_t *ret) {
+ uint64_t t;
+ int r;
+
+ if (!pidref_is_set(pid))
+ return -ESRCH;
+
+ r = pid_get_start_time(pid->pid, ret ? &t : NULL);
+ if (r < 0)
+ return r;
+
+ r = pidref_verify(pid);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = t;
+
+ return 0;
+}
+
int get_process_umask(pid_t pid, mode_t *ret) {
_cleanup_free_ char *m = NULL;
const char *p;
return -ESRCH;
result = pid_is_alive(pidref->pid);
- if (result < 0)
+ if (result < 0) {
+ assert(result != -ESRCH);
return result;
+ }
r = pidref_verify(pidref);
if (r == -ESRCH)
return 0;
}
+int pidref_safe_fork_full(
+ const char *name,
+ const int stdio_fds[3],
+ const int except_fds[],
+ size_t n_except_fds,
+ ForkFlags flags,
+ PidRef *ret_pid) {
+
+ pid_t pid;
+ int r, q;
+
+ assert(!FLAGS_SET(flags, FORK_WAIT));
+
+ r = safe_fork_full(name, stdio_fds, except_fds, n_except_fds, flags, &pid);
+ if (r < 0)
+ return r;
+
+ q = pidref_set_pid(ret_pid, pid);
+ if (q < 0) /* Let's not fail for this, no matter what, the process exists after all, and that's key */
+ *ret_pid = PIDREF_MAKE_FROM_PID(pid);
+
+ return r;
+}
+
int namespace_fork(
const char *outer_name,
const char *inner_name,
int get_process_root(pid_t pid, char **ret);
int get_process_environ(pid_t pid, char **ret);
int get_process_ppid(pid_t pid, pid_t *ret);
+int pid_get_start_time(pid_t pid, uint64_t *ret);
+int pidref_get_start_time(const PidRef* pid, uint64_t *ret);
int get_process_umask(pid_t pid, mode_t *ret);
int container_get_leader(const char *machine, pid_t *pid);
return safe_fork_full(name, NULL, NULL, 0, flags, ret_pid);
}
+int pidref_safe_fork_full(
+ const char *name,
+ const int stdio_fds[3],
+ const int except_fds[],
+ size_t n_except_fds,
+ ForkFlags flags,
+ PidRef *ret_pid);
+
+static inline int pidref_safe_fork(const char *name, ForkFlags flags, PidRef *ret_pid) {
+ return pidref_safe_fork_full(name, NULL, NULL, 0, flags, ret_pid);
+}
+
int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid);
int set_oom_score_adjust(int value);
return (int) n;
}
+int getpeerpidfd(int fd) {
+ socklen_t n = sizeof(int);
+ int pidfd = -EBADF;
+
+ assert(fd >= 0);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD, &pidfd, &n) < 0)
+ return -errno;
+
+ if (n != sizeof(int))
+ return -EIO;
+
+ return pidfd;
+}
+
ssize_t send_many_fds_iov_sa(
int transport_fd,
int *fds_array, size_t n_fds_array,
int getpeercred(int fd, struct ucred *ucred);
int getpeersec(int fd, char **ret);
int getpeergroups(int fd, gid_t **ret);
+int getpeerpidfd(int fd);
ssize_t send_many_fds_iov_sa(
int transport_fd,
return p + strlen(needle);
}
-char *startswith_strv(const char *string, char **strv) {
- char *found = NULL;
-
- STRV_FOREACH(i, strv) {
- found = startswith(string, *i);
- if (found)
- break;
- }
-
- return found;
-}
-
bool version_is_valid(const char *s) {
if (isempty(s))
return false;
return strchr(haystack, 0);
for (const char *p = haystack; *p; p++)
- if (strncmp(p, needle, l) == 0)
+ if (strneq(p, needle, l))
f = p;
return (char*) f;
char *find_line_startswith(const char *haystack, const char *needle);
-char *startswith_strv(const char *string, char **strv);
-
-#define STARTSWITH_SET(p, ...) \
- startswith_strv(p, STRV_MAKE(__VA_ARGS__))
-
bool version_is_valid(const char *s);
bool version_is_valid_versionspec(const char *s);
return strv_consume(l, x);
}
+char* startswith_strv(const char *s, char * const *l) {
+ STRV_FOREACH(i, l) {
+ char *found = startswith(s, *i);
+ if (found)
+ return found;
+ }
+
+ return NULL;
+}
+
+char* endswith_strv(const char *s, char * const *l) {
+ STRV_FOREACH(i, l) {
+ char *found = endswith(s, *i);
+ if (found)
+ return found;
+ }
+
+ return NULL;
+}
+
char** strv_reverse(char **l) {
size_t n;
}
DEFINE_HASH_OPS_FULL(string_strv_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free);
-
-char* strv_endswith(const char *s, char **l) {
- STRV_FOREACH(i, l) {
- char *e = endswith(s, *i);
- if (e)
- return (char*) e;
- }
-
- return NULL;
-}
strv_print_full(l, NULL);
}
+char* startswith_strv(const char *s, char * const *l);
+
+#define STARTSWITH_SET(p, ...) \
+ startswith_strv(p, STRV_MAKE(__VA_ARGS__))
+
+char* endswith_strv(const char *s, char * const *l);
+
+#define ENDSWITH_SET(p, ...) \
+ endswith_strv(p, STRV_MAKE(__VA_ARGS__))
+
#define strv_from_stdarg_alloca(first) \
({ \
char **_l; \
_x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \
})
-#define ENDSWITH_SET(p, ...) \
- ({ \
- const char *_p = (p); \
- char *_found = NULL; \
- STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \
- _found = endswith(_p, *_i); \
- if (_found) \
- break; \
- } \
- _found; \
- })
-
#define _FOREACH_STRING(uniq, x, y, ...) \
for (const char *x, * const*UNIQ_T(l, uniq) = STRV_MAKE_CONST(({ x = y; }), ##__VA_ARGS__); \
x; \
int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS);
#define string_strv_hashmap_put(h, k, v) _string_strv_hashmap_put(h, k, v HASHMAP_DEBUG_SRC_ARGS)
#define string_strv_ordered_hashmap_put(h, k, v) _string_strv_ordered_hashmap_put(h, k, v HASHMAP_DEBUG_SRC_ARGS)
-
-char* strv_endswith(const char *s, char **l);
assert(!m->subscribed);
m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ m->polkit_registry = hashmap_free(m->polkit_registry);
}
int bus_fdset_add_all(Manager *m, FDSet *fds) {
gid_t gid,
const char *tty,
char ***env, /* updated on success */
- const int fds[], size_t n_fds) {
+ const int fds[], size_t n_fds,
+ int exec_fd) {
#if HAVE_PAM
* those fds are open here that have been opened by PAM. */
(void) close_many(fds, n_fds);
+ /* Also close the 'exec_fd' in the child, since the service manager waits for the EOF induced
+ * by the execve() to wait for completion, and if we'd keep the fd open here in the child
+ * we'd never signal completion. */
+ exec_fd = safe_close(exec_fd);
+
/* Drop privileges - we don't need any to pam_close_session and this will make
* PR_SET_PDEATHSIG work in most cases. If this fails, ignore the error - but expect sd-pam
* threads to fail to exit normally */
* wins here. (See above.) */
/* All fds passed in the fds array will be closed in the pam child process. */
- r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, params->fds, n_fds);
+ r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, params->fds, n_fds, params->exec_fd);
if (r < 0) {
*exit_status = EXIT_PAM;
return log_exec_error_errno(context, params, r, "Failed to set up PAM session: %m");
#include <unistd.h>
#include "build.h"
+#include "bus-polkit.h"
#include "creds-util.h"
#include "dirent-util.h"
#include "escape.h"
{ "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodEncryptParameters, data), 0 },
{ "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, timestamp), 0 },
{ "notAfter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, not_after), 0 },
+ VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
_cleanup_(method_encrypt_parameters_done) MethodEncryptParameters p = {
.not_after = UINT64_MAX,
};
_cleanup_(iovec_done) struct iovec output = {};
+ Hashmap **polkit_registry = ASSERT_PTR(userdata);
int r;
assert(link);
if (p.not_after != UINT64_MAX && p.not_after < p.timestamp)
return varlink_error_invalid_parameter_name(link, "notAfter");
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ "io.systemd.credentials.encrypt",
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
r = encrypt_credential_and_warn(
arg_with_key,
p.name,
static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
static const JsonDispatch dispatch_table[] = {
- { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 },
- { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), 0 },
- { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 },
+ { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 },
+ { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), JSON_MANDATORY },
+ { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 },
+ VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
_cleanup_(method_decrypt_parameters_done) MethodDecryptParameters p = {
.timestamp = UINT64_MAX,
};
_cleanup_(iovec_done_erase) struct iovec output = {};
+ Hashmap **polkit_registry = ASSERT_PTR(userdata);
int r;
assert(link);
if (p.name && !credential_name_valid(p.name))
return varlink_error_invalid_parameter_name(link, "name");
- if (!p.blob.iov_base)
- return varlink_error_invalid_parameter_name(link, "blob");
if (p.timestamp == UINT64_MAX)
p.timestamp = now(CLOCK_REALTIME);
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ "io.systemd.credentials.decrypt",
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
r = decrypt_credential_and_warn(
p.name,
p.timestamp,
if (arg_varlink) {
_cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
+ _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL;
/* Invocation as Varlink service */
- r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_INHERIT_USERDATA);
+ r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m");
+ varlink_server_set_userdata(varlink_server, &polkit_registry);
+
r = varlink_server_loop_auto(varlink_server);
if (r < 0)
return log_error_errno(r, "Failed to run Varlink event loop: %m");
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+ SPDX-License-Identifier: LGPL-2.1-or-later
+
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+-->
+
+<policyconfig>
+
+ <vendor>The systemd Project</vendor>
+ <vendor_url>https://systemd.io</vendor_url>
+
+ <action id="io.systemd.credentials.encrypt">
+ <description gettext-domain="systemd">Allow encryption and signing of system credentials.</description>
+ <message gettext-domain="systemd">Authentication is required for an application to encrypt and sign a system credential.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+ <action id="io.systemd.credentials.decrypt">
+ <description gettext-domain="systemd">Allow decryption of system credentials.</description>
+ <message gettext-domain="systemd">Authentication is required for an application to decrypto a system credential.</message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+</policyconfig>
install_emptydir(sysconfdir / 'credstore.encrypted',
install_mode : 'rwx------')
endif
+
+install_data('io.systemd.credentials.policy',
+ install_dir : polkitpolicydir)
return (sd_char*) s + l;
}
-sd_char* endswith(const sd_char *s, const sd_char *postfix) {
+sd_char* endswith(const sd_char *s, const sd_char *suffix) {
size_t sl, pl;
assert(s);
- assert(postfix);
+ assert(suffix);
sl = strlen(s);
- pl = strlen(postfix);
+ pl = strlen(suffix);
if (pl == 0)
return (sd_char*) s + sl;
if (sl < pl)
return NULL;
- if (strcmp(s + sl - pl, postfix) != 0)
+ if (!streq(s + sl - pl, suffix))
return NULL;
return (sd_char*) s + sl - pl;
}
-sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) {
+sd_char* endswith_no_case(const sd_char *s, const sd_char *suffix) {
size_t sl, pl;
assert(s);
- assert(postfix);
+ assert(suffix);
sl = strlen(s);
- pl = strlen(postfix);
+ pl = strlen(suffix);
if (pl == 0)
return (sd_char*) s + sl;
if (sl < pl)
return NULL;
- if (strcasecmp(s + sl - pl, postfix) != 0)
+ if (!strcaseeq(s + sl - pl, suffix))
return NULL;
return (sd_char*) s + sl - pl;
sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_;
sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_;
-sd_char *endswith(const sd_char *s, const sd_char *postfix) _pure_;
-sd_char *endswith_no_case(const sd_char *s, const sd_char *postfix) _pure_;
+sd_char *endswith(const sd_char *s, const sd_char *suffix) _pure_;
+sd_char *endswith_no_case(const sd_char *s, const sd_char *suffix) _pure_;
static inline bool isempty(const sd_char *a) {
return !a || a[0] == '\0';
return 0;
}
-static void home_count_bad_authentication(Home *h, bool save) {
+static void home_count_bad_authentication(Home *h, int error, bool save) {
int r;
assert(h);
+ if (!IN_SET(error,
+ -ENOKEY, /* Password incorrect */
+ -EBADSLT, /* Password incorrect and no token */
+ -EREMOTEIO)) /* Recovery key incorrect */
+ return;
+
r = user_record_bad_authentication(h->record);
if (r < 0) {
log_warning_errno(r, "Failed to increase bad authentication counter, ignoring: %m");
secret = TAKE_PTR(h->secret); /* Take possession */
if (ret < 0) {
- if (ret == -ENOKEY)
- (void) home_count_bad_authentication(h, false);
+ (void) home_count_bad_authentication(h, ret, /* save= */ false);
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Fixation failed: %m");
home_set_state(h, HOME_UNFIXATED);
}
+static bool error_is_bad_password(int ret) {
+ /* Tests for the various cases of bad passwords. We generally don't want to log so loudly about
+ * these, since everyone types in a bad password now and then. Moreover we usually try to start out
+ * with an empty set of passwords, so the first authentication will frequently fail, if not token is
+ * inserted. */
+
+ return IN_SET(ret,
+ -ENOKEY, /* Bad password, or insufficient */
+ -EBADSLT, /* Bad password, and no token */
+ -EREMOTEIO, /* Bad recovery key */
+ -ENOANO, /* PIN for security token needed */
+ -ERFKILL, /* "Protected Authentication Path" for token needed */
+ -EMEDIUMTYPE, /* Presence confirmation on token needed */
+ -ENOCSI, /* User verification on token needed */
+ -ENOSTR, /* Token action timeout */
+ -EOWNERDEAD, /* PIN locked of security token */
+ -ENOLCK, /* Bad PIN of security token */
+ -ETOOMANYREFS, /* Bad PIN and few tries left */
+ -EUCLEAN); /* Bad PIN and one try left */
+}
+
static void home_activate_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(IN_SET(h->state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE));
if (ret < 0) {
- if (ret == -ENOKEY)
- home_count_bad_authentication(h, true);
+ (void) home_count_bad_authentication(h, ret, /* save= */ true);
(void) convert_worker_errno(h, ret, &error);
- r = log_error_errno(ret, "Activation failed: %m");
+ r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR,
+ ret, "Activation failed: %s", bus_error_message(&error, ret));
goto finish;
}
assert(h);
if (ret < 0) {
- if (ret == -ENOKEY)
- (void) home_count_bad_authentication(h, true);
+ (void) home_count_bad_authentication(h, ret, /* save= */ true);
(void) convert_worker_errno(h, ret, &error);
- r = log_error_errno(ret, "Change operation failed: %m");
+ r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR,
+ ret, "Change operation failed: %s", bus_error_message(&error, ret));
goto finish;
}
assert(IN_SET(h->state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE));
if (ret < 0) {
- if (ret == -ENOKEY)
- (void) home_count_bad_authentication(h, true);
+ (void) home_count_bad_authentication(h, ret, /* save= */ true);
(void) convert_worker_errno(h, ret, &error);
- r = log_error_errno(ret, "Unlocking operation failed: %m");
+ r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR,
+ ret, "Unlocking operation failed: %s", bus_error_message(&error, ret));
/* Revert to locked state */
home_set_state(h, HOME_LOCKED);
assert(IN_SET(h->state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE));
if (ret < 0) {
- if (ret == -ENOKEY)
- (void) home_count_bad_authentication(h, true);
+ (void) home_count_bad_authentication(h, ret, /* save= */ true);
(void) convert_worker_errno(h, ret, &error);
- r = log_error_errno(ret, "Authentication failed: %m");
+ r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR,
+ ret, "Authentication failed: %s", bus_error_message(&error, ret));
goto finish;
}
return sd_bus_message_close_container(reply);
}
+static int lookup_user_name(
+ Manager *m,
+ sd_bus_message *message,
+ const char *user_name,
+ sd_bus_error *error,
+ Home **ret) {
+
+ Home *h;
+ int r;
+
+ assert(m);
+ assert(message);
+ assert(user_name);
+ assert(ret);
+
+ if (isempty(user_name)) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ uid_t uid;
+
+ /* If an empty user name is specified, then identify caller's EUID and find home by that. */
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_euid(creds, &uid);
+ if (r < 0)
+ return r;
+
+ h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
+ if (!h)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "Client's UID " UID_FMT " not managed.", uid);
+
+ } else {
+
+ if (!valid_user_group_name(user_name, 0))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
+
+ h = hashmap_get(m->homes_by_name, user_name);
+ if (!h)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
+ }
+
+ *ret = h;
+ return 0;
+}
+
static int method_get_home_by_name(
sd_bus_message *message,
void *userdata,
r = sd_bus_message_read(message, "s", &user_name);
if (r < 0)
return r;
- if (!valid_user_group_name(user_name, 0))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
- h = hashmap_get(m->homes_by_name, user_name);
- if (!h)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
+ r = lookup_user_name(m, message, user_name, error, &h);
+ if (r < 0)
+ return r;
r = bus_home_path(h, &path);
if (r < 0)
r = sd_bus_message_read(message, "s", &user_name);
if (r < 0)
return r;
- if (!valid_user_group_name(user_name, 0))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
- h = hashmap_get(m->homes_by_name, user_name);
- if (!h)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
+ r = lookup_user_name(m, message, user_name, error, &h);
+ if (r < 0)
+ return r;
r = bus_home_get_record_json(h, message, &json, &incomplete);
if (r < 0)
if (r < 0)
return r;
- if (!valid_user_group_name(user_name, 0))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name);
-
- h = hashmap_get(m->homes_by_name, user_name);
- if (!h)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name);
+ r = lookup_user_name(m, message, user_name, error, &h);
+ if (r < 0)
+ return r;
return handler(message, h, error);
}
(void) home_wait_for_worker(h);
m->bus = sd_bus_flush_close_unref(m->bus);
- m->polkit_registry = bus_verify_polkit_async_registry_free(m->polkit_registry);
+ m->polkit_registry = hashmap_free(m->polkit_registry);
m->device_monitor = sd_device_monitor_unref(m->device_monitor);
send_interface="org.freedesktop.home1.Manager"
send_member="LockAllHomes"/>
+ <allow send_destination="org.freedesktop.home1"
+ send_interface="org.freedesktop.home1.Manager"
+ send_member="DeactivateAllHomes"/>
+
<allow send_destination="org.freedesktop.home1"
send_interface="org.freedesktop.home1.Manager"
send_member="Rebalance"/>
assert(c);
context_reset(c, UINT64_MAX);
- bus_verify_polkit_async_registry_free(c->polkit_registry);
+ hashmap_free(c->polkit_registry);
}
static void context_read_etc_hostname(Context *c) {
hashmap_free(m->transfers);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
m->bus = sd_bus_flush_close_unref(m->bus);
sd_event_unref(m->event);
c->x11_cache = sd_bus_message_unref(c->x11_cache);
c->vc_cache = sd_bus_message_unref(c->vc_cache);
- c->polkit_registry = bus_verify_polkit_async_registry_free(c->polkit_registry);
+ c->polkit_registry = hashmap_free(c->polkit_registry);
};
X11Context *context_get_x11_context(Context *c) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
_cleanup_close_ int fifo_fd = -EBADF;
_cleanup_free_ char *p = NULL;
- int r;
assert(s);
if (fifo_fd < 0)
return fifo_fd;
- r = session_watch_pidfd(s);
- if (r < 0)
- return r;
-
/* Update the session state file before we notify the client about the result. */
session_save(s);
#include "audit-util.h"
#include "bus-error.h"
#include "bus-util.h"
+#include "daemon-util.h"
#include "devnum-util.h"
#include "env-file.h"
#include "escape.h"
return 0;
}
-static void session_reset_leader(Session *s) {
+static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ Session *s = ASSERT_PTR(userdata);
+
+ assert(s->leader.fd == fd);
+ session_stop(s, /* force= */ false);
+
+ return 1;
+}
+
+static int session_watch_pidfd(Session *s) {
+ int r;
+
assert(s);
+ assert(s->manager);
+ assert(pidref_is_set(&s->leader));
+
+ if (s->leader.fd < 0)
+ return 0;
+
+ r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd");
+
+ return 0;
+}
+
+static void session_reset_leader(Session *s, bool keep_fdstore) {
+ assert(s);
+
+ if (!keep_fdstore) {
+ /* Clear fdstore if we're asked to, no matter if s->leader is set or not, so that when
+ * initially deserializing leader fd we clear the old fd too. */
+ (void) notify_remove_fd_warnf("session-%s-leader-fd", s->id);
+ s->leader_fd_saved = false;
+ }
if (!pidref_is_set(&s->leader))
return;
- assert_se(hashmap_remove_value(s->manager->sessions_by_leader, &s->leader, s));
+ s->leader_pidfd_event_source = sd_event_source_disable_unref(s->leader_pidfd_event_source);
+
+ (void) hashmap_remove_value(s->manager->sessions_by_leader, &s->leader, s);
return pidref_done(&s->leader);
}
free(s->scope_job);
- session_reset_leader(s);
+ session_reset_leader(s, /* keep_fdstore = */ true);
sd_bus_message_unref(s->create_message);
if (pidref_equal(&s->leader, &pidref))
return 0;
- session_reset_leader(s);
+ session_reset_leader(s, /* keep_fdstore = */ false);
s->leader = TAKE_PIDREF(pidref);
+ r = session_watch_pidfd(s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch leader pidfd for session '%s': %m", s->id);
+
r = hashmap_ensure_put(&s->manager->sessions_by_leader, &pidref_hash_ops, &s->leader, s);
if (r < 0)
return r;
assert(r > 0);
+ if (s->leader.fd >= 0) {
+ r = notify_push_fdf(s->leader.fd, "session-%s-leader-fd", s->id);
+ if (r < 0)
+ log_warning_errno(r, "Failed to push leader pidfd for session '%s', ignoring: %m", s->id);
+ else
+ s->leader_fd_saved = true;
+ }
+
(void) audit_session_from_pid(s->leader.pid, &s->audit_id);
return 1;
"# This is private data. Do not parse.\n"
"UID="UID_FMT"\n"
"USER=%s\n"
- "ACTIVE=%i\n"
- "IS_DISPLAY=%i\n"
+ "ACTIVE=%s\n"
+ "IS_DISPLAY=%s\n"
"STATE=%s\n"
- "REMOTE=%i\n",
+ "REMOTE=%s\n"
+ "LEADER_FD_SAVED=%s\n",
s->user->user_record->uid,
s->user->user_record->user_name,
- session_is_active(s),
- s->user->display == s,
+ one_zero(session_is_active(s)),
+ one_zero(s->user->display == s),
session_state_to_string(session_get_state(s)),
- s->remote);
+ one_zero(s->remote),
+ one_zero(s->leader_fd_saved));
if (s->type >= 0)
fprintf(f, "TYPE=%s\n", session_type_to_string(s->type));
*vtnr = NULL,
*state = NULL,
*position = NULL,
- *leader = NULL,
+ *leader_pid = NULL,
+ *leader_fd_saved = NULL,
*type = NULL,
*original_type = NULL,
*class = NULL,
assert(s);
r = parse_env_file(NULL, s->state_file,
- "REMOTE", &remote,
- "SCOPE", &s->scope,
- "SCOPE_JOB", &s->scope_job,
- "FIFO", &s->fifo_path,
- "SEAT", &seat,
- "TTY", &s->tty,
- "TTY_VALIDITY", &tty_validity,
- "DISPLAY", &s->display,
- "REMOTE_HOST", &s->remote_host,
- "REMOTE_USER", &s->remote_user,
- "SERVICE", &s->service,
- "DESKTOP", &s->desktop,
- "VTNR", &vtnr,
- "STATE", &state,
- "POSITION", &position,
- "LEADER", &leader,
- "TYPE", &type,
- "ORIGINAL_TYPE", &original_type,
- "CLASS", &class,
- "UID", &uid,
- "REALTIME", &realtime,
- "MONOTONIC", &monotonic,
- "CONTROLLER", &controller,
- "ACTIVE", &active,
- "DEVICES", &devices,
- "IS_DISPLAY", &is_display);
+ "REMOTE", &remote,
+ "SCOPE", &s->scope,
+ "SCOPE_JOB", &s->scope_job,
+ "FIFO", &s->fifo_path,
+ "SEAT", &seat,
+ "TTY", &s->tty,
+ "TTY_VALIDITY", &tty_validity,
+ "DISPLAY", &s->display,
+ "REMOTE_HOST", &s->remote_host,
+ "REMOTE_USER", &s->remote_user,
+ "SERVICE", &s->service,
+ "DESKTOP", &s->desktop,
+ "VTNR", &vtnr,
+ "STATE", &state,
+ "POSITION", &position,
+ "LEADER", &leader_pid,
+ "LEADER_FD_SAVED", &leader_fd_saved,
+ "TYPE", &type,
+ "ORIGINAL_TYPE", &original_type,
+ "CLASS", &class,
+ "UID", &uid,
+ "REALTIME", &realtime,
+ "MONOTONIC", &monotonic,
+ "CONTROLLER", &controller,
+ "ACTIVE", &active,
+ "DEVICES", &devices,
+ "IS_DISPLAY", &is_display);
if (r < 0)
return log_error_errno(r, "Failed to read %s: %m", s->state_file);
s->tty_validity = v;
}
- if (leader) {
- _cleanup_(pidref_done) PidRef p = PIDREF_NULL;
-
- r = pidref_set_pidstr(&p, leader);
- if (r < 0)
- log_debug_errno(r, "Failed to parse leader PID of session: %s", leader);
- else {
- r = session_set_leader_consume(s, TAKE_PIDREF(p));
- if (r < 0)
- log_warning_errno(r, "Failed to set session leader PID, ignoring: %m");
- }
- }
-
if (type) {
SessionType t;
session_restore_vt(s);
}
+ if (leader_pid) {
+ assert(!pidref_is_set(&s->leader));
+
+ r = parse_pid(leader_pid, &s->deserialized_pid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse LEADER=%s: %m", leader_pid);
+
+ if (leader_fd_saved) {
+ r = parse_boolean(leader_fd_saved);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse LEADER_FD_SAVED=%s: %m", leader_fd_saved);
+ s->leader_fd_saved = r > 0;
+
+ if (s->leader_fd_saved)
+ /* The leader fd will be acquired from fdstore later */
+ return 0;
+ }
+
+ _cleanup_(pidref_done) PidRef p = PIDREF_NULL;
+
+ r = pidref_set_pid(&p, s->deserialized_pid);
+ if (r >= 0)
+ r = session_set_leader_consume(s, TAKE_PIDREF(p));
+ if (r < 0)
+ log_warning_errno(r, "Failed to set leader PID for session '%s': %m", s->id);
+ }
+
return r;
}
LOG_MESSAGE("Removed session %s.", s->id));
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
- s->leader_pidfd_event_source = sd_event_source_unref(s->leader_pidfd_event_source);
if (s->seat)
seat_evict_position(s->seat, s);
seat_save(s->seat);
}
- session_reset_leader(s);
+ session_reset_leader(s, /* keep_fdstore = */ false);
user_save(s->user);
user_send_changed(s->user, "Display", NULL);
}
}
-static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
- Session *s = ASSERT_PTR(userdata);
-
- assert(s->leader.fd == fd);
- session_stop(s, /* force= */ false);
-
- return 1;
-}
-
-int session_watch_pidfd(Session *s) {
- int r;
-
- assert(s);
-
- if (s->leader.fd < 0)
- return 0;
-
- r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s);
- if (r < 0)
- return r;
-
- r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT);
- if (r < 0)
- return r;
-
- (void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd");
-
- return 0;
-}
-
bool session_may_gc(Session *s, bool drop_not_started) {
int r;
return true;
r = pidref_is_alive(&s->leader);
+ if (r == -ESRCH)
+ /* Session has no leader. This is probably because the leader vanished before deserializing
+ * pidfd from FD store. */
+ return true;
if (r < 0)
- log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not.", s->leader.pid);
+ log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not: %m", s->leader.pid);
if (r > 0)
return false;
- if (s->fifo_fd >= 0) {
- if (pipe_eof(s->fifo_fd) <= 0)
- return false;
- }
+ if (s->fifo_fd >= 0 && pipe_eof(s->fifo_fd) <= 0)
+ return false;
if (s->scope_job) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int vtfd;
PidRef leader;
+ bool leader_fd_saved; /* pidfd of leader uploaded to fdstore */
+ pid_t deserialized_pid; /* PID deserialized from state file (for verification when pidfd is used) */
uint32_t audit_id;
int fifo_fd;
int session_set_display(Session *s, const char *display);
int session_set_tty(Session *s, const char *tty);
int session_create_fifo(Session *s);
-int session_watch_pidfd(Session *s);
int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error);
int session_stop(Session *s, bool force);
int session_finalize(Session *s);
{ "IOWeight", u->user_record->io_weight },
};
- for (size_t i = 0; i < ELEMENTSOF(settings); i++)
- if (settings[i].value != UINT64_MAX) {
- r = sd_bus_message_append(m, "(sv)", settings[i].name, "t", settings[i].value);
- if (r < 0)
- return bus_log_create_error(r);
- }
+ FOREACH_ARRAY(st, settings, ELEMENTSOF(settings)) {
+ if (st->value == UINT64_MAX)
+ continue;
+
+ r = sd_bus_message_append(m, "(sv)", st->name, "t", st->value);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
r = sd_bus_message_close_container(m);
if (r < 0)
#include "constants.h"
#include "daemon-util.h"
#include "device-util.h"
+#include "devnum-util.h"
#include "dirent-util.h"
#include "escape.h"
#include "fd-util.h"
if (m->unlink_nologin)
(void) unlink_or_warn("/run/nologin");
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
sd_event_unref(m->event);
if (r < 0)
return r;
- FOREACH_DEVICE(e, d) {
- int k;
+ r = 0;
- k = manager_process_seat_device(m, d);
- if (k < 0)
- r = k;
- }
+ FOREACH_DEVICE(e, d)
+ RET_GATHER(r, manager_process_seat_device(m, d));
return r;
}
if (r < 0)
return r;
- FOREACH_DEVICE(e, d) {
- int k;
+ r = 0;
- k = manager_process_button_device(m, d);
- if (k < 0)
- r = k;
- }
+ FOREACH_DEVICE(e, d)
+ RET_GATHER(r, manager_process_button_device(m, d));
return r;
}
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/seats: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/seats/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
Seat *s;
- int k;
if (!dirent_is_file(de))
continue;
continue;
}
- k = seat_load(s);
- if (k < 0)
- r = k;
+ RET_GATHER(r, seat_load(s));
}
return r;
}
FOREACH_DIRENT(de, d, return -errno) {
- int k;
_cleanup_free_ char *n = NULL;
+ int k;
if (!dirent_is_file(de))
continue;
+
k = cunescape(de->d_name, 0, &n);
if (k < 0) {
- r = log_warning_errno(k, "Failed to unescape username '%s', ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to unescape username '%s', ignoring: %m", de->d_name));
continue;
}
+
k = manager_add_user_by_name(m, n, NULL);
if (k < 0)
- r = log_warning_errno(k, "Couldn't add lingering user %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Couldn't add lingering user %s, ignoring: %m", de->d_name));
}
return r;
static int manager_enumerate_users(Manager *m) {
_cleanup_closedir_ DIR *d = NULL;
- int r, k;
+ int r;
assert(m);
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/users: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/users/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
User *u;
uid_t uid;
+ int k;
if (!dirent_is_file(de))
continue;
k = parse_uid(de->d_name, &uid);
if (k < 0) {
- r = log_warning_errno(k, "Failed to parse filename /run/systemd/users/%s as UID.", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to parse filename /run/systemd/users/%s as UID, ignoring: %m", de->d_name));
continue;
}
k = manager_add_user_by_uid(m, uid, &u);
if (k < 0) {
- r = log_warning_errno(k, "Failed to add user by file name %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to add user by filename %s, ignoring: %m", de->d_name));
continue;
}
user_add_to_gc_queue(u);
- k = user_load(u);
- if (k < 0)
- r = k;
+ RET_GATHER(r, user_load(u));
}
return r;
}
-static int parse_fdname(const char *fdname, char **session_id, dev_t *dev) {
+static int parse_fdname(const char *fdname, char **ret_session_id, dev_t *ret_devno) {
_cleanup_strv_free_ char **parts = NULL;
_cleanup_free_ char *id = NULL;
- unsigned major, minor;
int r;
+ assert(ret_session_id);
+ assert(ret_devno);
+
parts = strv_split(fdname, "-");
if (!parts)
return -ENOMEM;
- if (strv_length(parts) != 5)
- return -EINVAL;
- if (!streq(parts[0], "session"))
+ if (_unlikely_(!streq(parts[0], "session")))
return -EINVAL;
id = strdup(parts[1]);
if (!id)
return -ENOMEM;
- if (!streq(parts[2], "device"))
+ if (streq(parts[2], "leader")) {
+ *ret_session_id = TAKE_PTR(id);
+ *ret_devno = 0;
+
+ return 0;
+ }
+
+ if (_unlikely_(!streq(parts[2], "device")))
return -EINVAL;
+ unsigned major, minor;
+
r = safe_atou(parts[3], &major);
if (r < 0)
return r;
+
r = safe_atou(parts[4], &minor);
if (r < 0)
return r;
- *dev = makedev(major, minor);
- *session_id = TAKE_PTR(id);
+ *ret_session_id = TAKE_PTR(id);
+ *ret_devno = makedev(major, minor);
return 0;
}
-static int deliver_fd(Manager *m, const char *fdname, int fd) {
- _cleanup_free_ char *id = NULL;
+static int deliver_session_device_fd(Session *s, const char *fdname, int fd, dev_t devno) {
SessionDevice *sd;
struct stat st;
- Session *s;
- dev_t dev;
- int r;
- assert(m);
+ assert(s);
+ assert(fdname);
assert(fd >= 0);
-
- r = parse_fdname(fdname, &id, &dev);
- if (r < 0)
- return log_debug_errno(r, "Failed to parse fd name %s: %m", fdname);
-
- s = hashmap_get(m->sessions, id);
- if (!s)
- /* If the session doesn't exist anymore, the associated session device attached to this fd
- * doesn't either. Let's simply close this fd. */
- return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Failed to attach fd for unknown session: %s", id);
+ assert(devno > 0);
if (fstat(fd, &st) < 0)
/* The device is allowed to go away at a random point, in which case fstat() failing is
* expected. */
- return log_debug_errno(errno, "Failed to stat device fd for session %s: %m", id);
+ return log_debug_errno(errno, "Failed to stat device fd '%s' for session '%s': %m",
+ fdname, s->id);
- if (!S_ISCHR(st.st_mode) || st.st_rdev != dev)
- return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "Device fd doesn't point to the expected character device node");
+ if (!S_ISCHR(st.st_mode) || st.st_rdev != devno)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENODEV),
+ "Device fd '%s' doesn't point to the expected character device node.",
+ fdname);
- sd = hashmap_get(s->devices, &dev);
+ sd = hashmap_get(s->devices, &devno);
if (!sd)
/* Weird, we got an fd for a session device which wasn't recorded in the session state
* file... */
- return log_warning_errno(SYNTHETIC_ERRNO(ENODEV), "Got fd for missing session device [%u:%u] in session %s",
- major(dev), minor(dev), s->id);
+ return log_warning_errno(SYNTHETIC_ERRNO(ENODEV),
+ "Got session device fd '%s' [" DEVNUM_FORMAT_STR "], but not present in session state.",
+ fdname, DEVNUM_FORMAT_VAL(devno));
- log_debug("Attaching fd to session device [%u:%u] for session %s",
- major(dev), minor(dev), s->id);
+ log_debug("Attaching session device fd '%s' [" DEVNUM_FORMAT_STR "] to session '%s'.",
+ fdname, DEVNUM_FORMAT_VAL(devno), s->id);
session_device_attach_fd(sd, fd, s->was_active);
return 0;
}
-static int manager_attach_fds(Manager *m) {
- _cleanup_strv_free_ char **fdnames = NULL;
- int n;
+static int deliver_session_leader_fd_consume(Session *s, const char *fdname, int fd) {
+ _cleanup_(pidref_done) PidRef leader_fdstore = PIDREF_NULL;
+ int r;
- /* Upon restart, PID1 will send us back all fds of session devices that we previously opened. Each
- * file descriptor is associated with a given session. The session ids are passed through FDNAMES. */
+ assert(s);
+ assert(fdname);
+ assert(fd >= 0);
- assert(m);
+ if (!s->leader_fd_saved)
+ log_warning("Got leader pidfd for session '%s', but not recorded in session state, proceeding anyway.",
+ s->id);
+ else
+ assert(!pidref_is_set(&s->leader));
- n = sd_listen_fds_with_names(true, &fdnames);
- if (n < 0)
- return log_warning_errno(n, "Failed to acquire passed fd list: %m");
- if (n == 0)
- return 0;
+ r = pidref_set_pidfd_take(&leader_fdstore, fd);
+ if (r < 0) {
+ if (r == -ESRCH)
+ log_debug_errno(r, "Leader of session '%s' is gone while deserializing.", s->id);
+ else
+ log_warning_errno(r, "Failed to create reference to leader of session '%s': %m", s->id);
- for (int i = 0; i < n; i++) {
- int fd = SD_LISTEN_FDS_START + i;
+ close_and_notify_warn(fd, fdname);
+ return r;
+ }
- if (deliver_fd(m, fdnames[i], fd) >= 0)
- continue;
+ assert(pid_is_valid(s->deserialized_pid));
- /* Hmm, we couldn't deliver the fd to any session device object? If so, let's close the fd
- * and remove it from fdstore. */
- close_and_notify_warn(fd, fdnames[i]);
- }
+ if (leader_fdstore.pid != s->deserialized_pid)
+ log_warning("Leader from pidfd (" PID_FMT ") doesn't match with LEADER=" PID_FMT " for session '%s', proceeding anyway.",
+ leader_fdstore.pid, s->deserialized_pid, s->id);
+
+ r = session_set_leader_consume(s, TAKE_PIDREF(leader_fdstore));
+ if (r < 0)
+ return log_warning_errno(r, "Failed to attach leader pidfd for session '%s': %m", s->id);
return 0;
}
+static int manager_attach_session_fd_one_consume(Manager *m, const char *fdname, int fd) {
+ _cleanup_free_ char *id = NULL;
+ dev_t devno;
+ Session *s;
+ int r;
+
+ assert(m);
+ assert(fdname);
+ assert(fd >= 0);
+
+ r = parse_fdname(fdname, &id, &devno);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse stored fd name '%s': %m", fdname);
+ goto fail_close;
+ }
+
+ s = hashmap_get(m->sessions, id);
+ if (!s) {
+ /* If the session doesn't exist anymore, let's simply close this fd. */
+ r = log_debug_errno(SYNTHETIC_ERRNO(ENXIO),
+ "Cannot attach fd '%s' to unknown session '%s', ignoring.", fdname, id);
+ goto fail_close;
+ }
+
+ if (devno > 0) {
+ r = deliver_session_device_fd(s, fdname, fd, devno);
+ if (r < 0)
+ goto fail_close;
+ return 0;
+ }
+
+ /* Takes ownership of fd on both success and failure */
+ return deliver_session_leader_fd_consume(s, fdname, fd);
+
+fail_close:
+ close_and_notify_warn(fd, fdname);
+ return r;
+}
+
static int manager_enumerate_sessions(Manager *m) {
+ _cleanup_strv_free_ char **fdnames = NULL;
_cleanup_closedir_ DIR *d = NULL;
- int r = 0, k;
+ int r = 0, n;
assert(m);
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/sessions: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/sessions/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
- struct Session *s;
+ Session *s;
+ int k;
if (!dirent_is_file(de))
continue;
k = manager_add_session(m, de->d_name, &s);
if (k < 0) {
- r = log_warning_errno(k, "Failed to add session by file name %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Failed to add session by filename %s, ignoring: %m", de->d_name));
continue;
}
k = session_load(s);
if (k < 0)
- r = k;
+ RET_GATHER(r, log_warning_errno(k, "Failed to deserialize session '%s', ignoring: %m", s->id));
}
- /* We might be restarted and PID1 could have sent us back the session device fds we previously
- * saved. */
- (void) manager_attach_fds(m);
+ n = sd_listen_fds_with_names(/* unset_environment = */ true, &fdnames);
+ if (n < 0)
+ return log_error_errno(n, "Failed to acquire passed fd list: %m");
+
+ for (int i = 0; i < n; i++) {
+ int fd = SD_LISTEN_FDS_START + i;
+
+ RET_GATHER(r, manager_attach_session_fd_one_consume(m, fdnames[i], fd));
+ }
return r;
}
if (errno == ENOENT)
return 0;
- return log_error_errno(errno, "Failed to open /run/systemd/inhibit: %m");
+ return log_error_errno(errno, "Failed to open /run/systemd/inhibit/: %m");
}
FOREACH_DIRENT(de, d, return -errno) {
- int k;
Inhibitor *i;
+ int k;
if (!dirent_is_file(de))
continue;
k = manager_add_inhibitor(m, de->d_name, &i);
if (k < 0) {
- r = log_warning_errno(k, "Couldn't add inhibitor %s, ignoring: %m", de->d_name);
+ RET_GATHER(r, log_warning_errno(k, "Couldn't add inhibitor %s, ignoring: %m", de->d_name));
continue;
}
- k = inhibitor_load(i);
- if (k < 0)
- r = k;
+ RET_GATHER(r, inhibitor_load(i));
}
return r;
class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
- tty = strempty(tty);
-
- if (strchr(tty, ':')) {
+ 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
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
* 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);
sd_event_source_unref(m->nscd_cache_flush_event);
#endif
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
manager_varlink_done(m);
static NetDev *netdev_free(NetDev *netdev) {
assert(netdev);
- netdev_detach_from_manager(netdev);
-
- free(netdev->filename);
-
- free(netdev->description);
- free(netdev->ifname);
- condition_free_list(netdev->conditions);
-
/* Invoke the per-kind done() destructor, but only if the state field is initialized. We conditionalize that
* because we parse .netdev files twice: once to determine the kind (with a short, minimal NetDev structure
* allocation, with no room for per-kind fields), and once to read the kind's properties (with a full,
NETDEV_VTABLE(netdev)->done)
NETDEV_VTABLE(netdev)->done(netdev);
+ netdev_detach_from_manager(netdev);
+
+ condition_free_list(netdev->conditions);
+ free(netdev->filename);
+ free(netdev->description);
+ free(netdev->ifname);
+
return mfree(netdev);
}
sd_device_monitor_unref(m->device_monitor);
manager_varlink_done(m);
-
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
free(m->dynamic_timezone);
}
r = config_parse_mtu(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->mtu, userdata);
- if (r < 0)
+ if (r <= 0)
return r;
TAKE_PTR(n);
sd_event_source_unref(m->mem_pressure_context_event_source);
sd_event_unref(m->event);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
hashmap_free(m->monitored_swap_cgroup_contexts);
* policies).
*
* Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a
- * PolicyAuthorizeNV epxression that pins the NV index in the policy, and permits access to any
+ * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any
* policies matching the current NV index contents.
*
* We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we
sd_event_source_unref(m->image_cache_defer_event);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
sd_event_unref(m->event);
ordered_set_free(m->dns_extra_stub_listeners);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
sd_bus_flush_close_unref(m->bus);
#include "bus-message.h"
#include "bus-polkit.h"
#include "bus-util.h"
+#include "process-util.h"
#include "strv.h"
#include "user-util.h"
-static int check_good_user(sd_bus_message *m, uid_t good_user) {
+static int bus_message_check_good_user(sd_bus_message *m, uid_t good_user) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
uid_t sender_uid;
int r;
assert(m);
if (good_user == UID_INVALID)
- return 0;
+ return false;
r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds);
if (r < 0)
return r;
}
-static int bus_message_new_polkit_auth_call(
+static int bus_message_new_polkit_auth_call_for_bus(
sd_bus_message *m,
const char *action,
const char **details,
/* Tests non-interactively! */
- r = check_good_user(call, good_user);
+ r = bus_message_check_good_user(call, good_user);
if (r != 0)
return r;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL, *reply = NULL;
int authorized = false, challenge = false;
- r = bus_message_new_polkit_auth_call(call, action, details, /* interactive = */ false, &request);
+ r = bus_message_new_polkit_auth_call_for_bus(call, action, details, /* interactive = */ false, &request);
if (r < 0)
return r;
AsyncPolkitQueryAction *action;
+ sd_bus *bus;
sd_bus_message *request;
sd_bus_slot *slot;
+ Varlink *link;
Hashmap *registry;
sd_event_source *defer_event_source;
sd_bus_message_unref(q->request);
+ sd_bus_unref(q->bus);
+ varlink_unref(q->link);
+
async_polkit_query_action_free(q->action);
sd_event_source_disable_unref(q->defer_event_source);
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(AsyncPolkitQuery, async_polkit_query, async_polkit_query_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQuery*, async_polkit_query_unref);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ async_polkit_query_hash_ops,
+ void,
+ trivial_hash_func,
+ trivial_compare_func,
+ AsyncPolkitQuery,
+ async_polkit_query_unref);
+
static int async_polkit_defer(sd_event_source *s, void *userdata) {
AsyncPolkitQuery *q = ASSERT_PTR(userdata);
if (!q->defer_event_source) {
r = sd_event_add_defer(
- sd_bus_get_event(sd_bus_message_get_bus(reply)),
+ sd_bus_get_event(q->bus),
&q->defer_event_source,
async_polkit_defer,
q);
if (r < 0)
return r;
- r = sd_bus_message_rewind(q->request, true);
- if (r < 0)
- return r;
+ if (q->request) {
+ r = sd_bus_message_rewind(q->request, true);
+ if (r < 0)
+ return r;
- r = sd_bus_enqueue_for_read(sd_bus_message_get_bus(q->request), q->request);
- if (r < 0)
- return r;
+ r = sd_bus_enqueue_for_read(q->bus, q->request);
+ if (r < 0)
+ return r;
+ }
+
+ if (q->link) {
+ r = varlink_dispatch_again(q->link);
+ if (r < 0)
+ return r;
+ }
return 1;
}
r = async_polkit_process_reply(reply, q);
if (r < 0) {
log_debug_errno(r, "Processing asynchronous PolicyKit reply failed, ignoring: %m");
- (void) sd_bus_reply_method_errno(q->request, r, NULL);
+ if (q->request)
+ (void) sd_bus_reply_method_errno(q->request, r, NULL);
+ if (q->link)
+ varlink_error_errno(q->link, r);
async_polkit_query_unref(q);
}
return r;
assert(q);
assert(action);
- assert(ret_error);
LIST_FOREACH(authorized, a, q->authorized_actions)
if (streq(a->action, action) && strv_equal(a->details, (char**) details))
- return 1;
+ return 1; /* Allow! */
if (q->error_action && streq(q->error_action->action, action))
return sd_bus_error_copy(ret_error, &q->error);
assert(registry);
assert(ret_error);
- r = check_good_user(call, good_user);
+ r = bus_message_check_good_user(call, good_user);
if (r != 0)
return r;
if (c > 0)
interactive = true;
- r = hashmap_ensure_allocated(registry, NULL);
- if (r < 0)
- return r;
-
- r = bus_message_new_polkit_auth_call(call, action, details, interactive, &pk);
+ r = bus_message_new_polkit_auth_call_for_bus(call, action, details, interactive, &pk);
if (r < 0)
return r;
*q = (AsyncPolkitQuery) {
.n_ref = 1,
.request = sd_bus_message_ref(call),
+ .bus = sd_bus_ref(sd_bus_message_get_bus(call)),
};
}
return -ENOMEM;
if (!q->registry) {
- r = hashmap_put(*registry, call, q);
+ r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, call, q);
if (r < 0)
return r;
return -EACCES;
}
-Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry) {
+static int varlink_check_good_user(Varlink *link, uid_t good_user) {
+ int r;
+
+ assert(link);
+
+ if (good_user == UID_INVALID)
+ return false;
+
+ uid_t peer_uid;
+ r = varlink_get_peer_uid(link, &peer_uid);
+ if (r < 0)
+ return r;
+
+ return good_user == peer_uid;
+}
+
+static int varlink_check_peer_privilege(Varlink *link) {
+ int r;
+
+ assert(link);
+
+ uid_t peer_uid;
+ r = varlink_get_peer_uid(link, &peer_uid);
+ if (r < 0)
+ return r;
+
+ uid_t our_uid = getuid();
+ return peer_uid == our_uid ||
+ (our_uid != 0 && peer_uid == 0);
+}
+
+#if ENABLE_POLKIT
+static int bus_message_new_polkit_auth_call_for_varlink(
+ sd_bus *bus,
+ Varlink *link,
+ const char *action,
+ const char **details,
+ bool interactive,
+ sd_bus_message **ret) {
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL;
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ int r;
+
+ assert(bus);
+ assert(link);
+ assert(action);
+ assert(ret);
+
+ r = varlink_get_peer_pidref(link, &pidref);
+ if (r < 0)
+ return r;
+ if (r == 0) /* if we couldn't get a pidfd this returns == 0 */
+ return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Failed to get peer pidfd, cannot securely authenticate.");
+
+ uid_t uid;
+ r = varlink_get_peer_uid(link, &uid);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &c,
+ "org.freedesktop.PolicyKit1",
+ "/org/freedesktop/PolicyKit1/Authority",
+ "org.freedesktop.PolicyKit1.Authority",
+ "CheckAuthorization");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(
+ c,
+ "(sa{sv})s",
+ "unix-process", 2,
+ "pidfd", "h", (uint32_t) pidref.fd,
+ "uid", "i", (int32_t) uid,
+ action);
+ if (r < 0)
+ return r;
+
+ r = bus_message_append_strv_key_value(c, details);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(c, "us", interactive, NULL);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(c);
+ return 0;
+}
+
+static bool varlink_allow_interactive_authentication(Varlink *link) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ int r;
+
+ assert(link);
+
+ /* We look for the allowInteractiveAuthentication field in the message currently being dispatched,
+ * always under the same name. */
+
+ r = varlink_get_current_parameters(link, &v);
+ if (r < 0)
+ return r;
+
+ JsonVariant *b;
+ b = json_variant_by_key(v, "allowInteractiveAuthentication");
+ if (b) {
+ if (json_variant_is_boolean(b))
+ return json_variant_boolean(b);
+
+ log_debug("Incoming 'allowInteractiveAuthentication' field is not a boolean, ignoring.");
+ }
+
+ return false;
+}
+#endif
+
+int varlink_verify_polkit_async(
+ Varlink *link,
+ sd_bus *bus,
+ const char *action,
+ const char **details,
+ uid_t good_user,
+ Hashmap **registry) {
+
+ int r;
+
+ assert(link);
+ assert(registry);
+
+ /* This is the same as bus_verify_polkit_async_full(), but authenticates the peer of a varlink
+ * connection rather than the sender of a bus message. */
+
+ r = varlink_check_good_user(link, good_user);
+ if (r != 0)
+ return r;
+
+ r = varlink_check_peer_privilege(link);
+ if (r != 0)
+ return r;
+
#if ENABLE_POLKIT
- return hashmap_free_with_destructor(registry, async_polkit_query_unref);
-#else
- assert(hashmap_isempty(registry));
- return hashmap_free(registry);
+ _cleanup_(async_polkit_query_unrefp) AsyncPolkitQuery *q = NULL;
+
+ q = async_polkit_query_ref(hashmap_get(*registry, link));
+ /* This is a repeated invocation of this function, hence let's check if we've already got
+ * a response from polkit for this action */
+ if (q) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ r = async_polkit_query_check_action(q, action, details, &error);
+ if (r < 0) {
+ /* Reply with a nice error */
+ if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED))
+ return varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL);
+
+ if (ERRNO_IS_NEG_PRIVILEGE(r))
+ return varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL);
+
+ return r;
+ }
+ if (r > 0)
+ return r;
+ }
+
+ _cleanup_(sd_bus_unrefp) sd_bus *mybus = NULL;
+ if (!bus) {
+ r = sd_bus_open_system(&mybus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_attach_event(mybus, varlink_get_event(link), 0);
+ if (r < 0)
+ return r;
+
+ bus = mybus;
+ }
+
+ bool interactive = varlink_allow_interactive_authentication(link);
+
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
+ r = bus_message_new_polkit_auth_call_for_varlink(bus, link, action, details, interactive, &pk);
+ if (r < 0)
+ return r;
+
+ if (!q) {
+ q = new(AsyncPolkitQuery, 1);
+ if (!q)
+ return -ENOMEM;
+
+ *q = (AsyncPolkitQuery) {
+ .n_ref = 1,
+ .link = varlink_ref(link),
+ .bus = sd_bus_ref(bus),
+ };
+ }
+
+ assert(!q->action);
+ q->action = new(AsyncPolkitQueryAction, 1);
+ if (!q->action)
+ return -ENOMEM;
+
+ *q->action = (AsyncPolkitQueryAction) {
+ .action = strdup(action),
+ .details = strv_copy((char**) details),
+ };
+ if (!q->action->action || !q->action->details)
+ return -ENOMEM;
+
+ if (!q->registry) {
+ r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, link, q);
+ if (r < 0)
+ return r;
+
+ q->registry = *registry;
+ }
+
+ r = sd_bus_call_async(bus, &q->slot, pk, async_polkit_callback, q, 0);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(q);
+
+ return 0;
#endif
+
+ return -EACCES;
}
#include "hashmap.h"
#include "user-util.h"
+#include "varlink.h"
int bus_test_polkit(sd_bus_message *call, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e);
return bus_verify_polkit_async_full(call, action, details, false, UID_INVALID, registry, ret_error);
}
-Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry);
+int varlink_verify_polkit_async(Varlink *link, sd_bus *bus, const char *action, const char **details, uid_t good_user, Hashmap **registry);
+
+/* A JsonDispatch initializer that makes sure the allowInteractiveAuthentication boolean field we want for
+ * polkit support in Varlink calls is ignored while regular dispatching (and does not result in errors
+ * regarding unexpected fields) */
+#define VARLINK_DISPATCH_POLKIT_FIELD { \
+ .name = "allowInteractiveAuthentication", \
+ .type = JSON_VARIANT_BOOLEAN, \
+ }
return 0;
}
- return 0;
+ return 1;
}
int config_parse_rlimit(
return r;
if (format_suffixes) {
- char *e = strv_endswith(name, format_suffixes);
+ char *e = endswith_strv(name, format_suffixes);
if (!e) /* Format suffix is required */
return -EINVAL;
return 0;
}
+static bool json_variant_is_sensitive_recursive(JsonVariant *v) {
+ if (!v)
+ return false;
+ if (json_variant_is_sensitive(v))
+ return true;
+ if (v == JSON_VARIANT_MAGIC_EMPTY_ARRAY ||
+ v == JSON_VARIANT_MAGIC_EMPTY_OBJECT)
+ return false;
+ if (!json_variant_is_regular(v))
+ return false;
+ if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT))
+ return false;
+ if (v->is_reference)
+ return json_variant_is_sensitive_recursive(v->reference);
+
+ for (size_t i = 0; i < json_variant_elements(v); i++)
+ if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i)))
+ return true;
+
+ return false;
+}
+
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) {
_cleanup_(memstream_done) MemStream m = {};
size_t sz;
if (flags & JSON_FORMAT_OFF)
return -ENOEXEC;
+ if ((flags & JSON_FORMAT_REFUSE_SENSITIVE))
+ if (json_variant_is_sensitive_recursive(v))
+ return -EPERM;
+
f = memstream_init(&m);
if (!f)
return -ENOMEM;
int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column);
typedef enum JsonFormatFlags {
- JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
- JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */
- JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */
- JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */
- JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */
- JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */
- JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */
- JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */
- JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
- JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */
- JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */
+ JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
+ JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */
+ JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */
+ JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */
+ JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */
+ JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */
+ JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */
+ JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */
+ JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
+ JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */
+ JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */
+ JSON_FORMAT_REFUSE_SENSITIVE = 1 << 11, /* return EPERM if any node in the tree is marked as senstitive */
} JsonFormatFlags;
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret);
JsonVariant *current;
VarlinkSymbol *current_method;
+ int peer_pidfd;
struct ucred ucred;
bool ucred_acquired:1;
.timeout = VARLINK_DEFAULT_TIMEOUT_USEC,
.af = -1,
+
+ .peer_pidfd = -EBADF,
};
*ret = v;
sigterm_wait(v->exec_pid);
v->exec_pid = 0;
}
+
+ v->peer_pidfd = safe_close(v->peer_pidfd);
}
static Varlink* varlink_destroy(Varlink *v) {
sz = e - begin + 1;
- varlink_log(v, "New incoming message: %s", begin); /* FIXME: should we output the whole message here before validation?
- * This may produce a non-printable journal entry if the message
- * is invalid. We may also expose privileged information. */
-
r = json_parse(begin, 0, &v->current, NULL, NULL);
if (r < 0) {
/* If we encounter a parse failure flush all data. We cannot possibly recover from this,
return r;
}
+int varlink_dispatch_again(Varlink *v) {
+ int r;
+
+ assert_return(v, -EINVAL);
+
+ /* If a method call handler could not process the method call just yet (for example because it needed
+ * some Polkit authentication first), then it can leave the call unanswered, do its thing, and then
+ * ask to be dispatched a second time, via this call. It will then be called again, for the same
+ * message */
+
+ if (v->state == VARLINK_DISCONNECTED)
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected.");
+ if (v->state != VARLINK_PENDING_METHOD)
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection has no pending method.");
+
+ varlink_set_state(v, VARLINK_IDLE_SERVER);
+
+ r = sd_event_source_set_enabled(v->defer_event_source, SD_EVENT_ON);
+ if (r < 0)
+ return varlink_log_errno(v, r, "Failed to enable deferred event source: %m");
+
+ return 0;
+}
+
+int varlink_get_current_parameters(Varlink *v, JsonVariant **ret) {
+ JsonVariant *p;
+
+ assert_return(v, -EINVAL);
+
+ if (!v->current)
+ return -ENODATA;
+
+ p = json_variant_by_key(v->current, "parameters");
+ if (!p)
+ return -ENODATA;
+
+ if (ret)
+ *ret = json_variant_ref(p);
+
+ return 0;
+}
+
static void handle_revents(Varlink *v, int revents) {
assert(v);
static int varlink_format_json(Varlink *v, JsonVariant *m) {
_cleanup_(erase_and_freep) char *text = NULL;
+ bool sensitive = false;
int r;
assert(v);
assert(m);
- r = json_variant_format(m, 0, &text);
+ r = json_variant_format(m, JSON_FORMAT_REFUSE_SENSITIVE, &text);
+ if (r == -EPERM) {
+ sensitive = true;
+ r = json_variant_format(m, /* flags= */ 0, &text);
+ }
if (r < 0)
return r;
assert(text[r] == '\0');
if (v->output_buffer_size + r + 1 > VARLINK_BUFFER_MAX)
return -ENOBUFS;
- varlink_log(v, "Sending message: %s", text);
+ varlink_log(v, "Sending message: %s", sensitive ? "<sensitive data>" : text);
if (v->output_buffer_size == 0) {
v->output_buffer_index = 0;
}
- if (json_variant_is_sensitive(m))
+ if (sensitive)
v->output_buffer_sensitive = true; /* Propagate sensitive flag */
else
text = mfree(text); /* No point in the erase_and_free() destructor declared above */
return 0;
}
+static int varlink_acquire_pidfd(Varlink *v) {
+ assert(v);
+
+ if (v->peer_pidfd >= 0)
+ return 0;
+
+ v->peer_pidfd = getpeerpidfd(v->fd);
+ if (v->peer_pidfd < 0)
+ return v->peer_pidfd;
+
+ return 0;
+}
+
+int varlink_get_peer_pidref(Varlink *v, PidRef *ret) {
+ int r;
+
+ assert_return(v, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ /* Returns r > 0 if we acquired the pidref via SO_PEERPIDFD (i.e. if we can use it for
+ * authentication). Returns == 0 if we didn't, and the pidref should not be used for
+ * authentication. */
+
+ r = varlink_acquire_pidfd(v);
+ if (r < 0)
+ return r;
+
+ if (v->peer_pidfd < 0) {
+ pid_t pid;
+
+ r = varlink_get_peer_pid(v, &pid);
+ if (r < 0)
+ return r;
+
+ r = pidref_set_pid(ret, pid);
+ if (r < 0)
+ return r;
+
+ return 0; /* didn't get pidfd securely */
+ }
+
+ r = pidref_set_pidfd(ret, v->peer_pidfd);
+ if (r < 0)
+ return r;
+
+ return 1; /* got pidfd securely */
+}
+
int varlink_set_relative_timeout(Varlink *v, usec_t timeout) {
assert_return(v, -EINVAL);
assert_return(timeout > 0, -EINVAL);
#include "sd-event.h"
#include "json.h"
+#include "pidref.h"
#include "time-util.h"
#include "varlink-idl.h"
int varlink_notify(Varlink *v, JsonVariant *parameters);
int varlink_notifyb(Varlink *v, ...);
+/* Ask for the current message to be dispatched again */
+int varlink_dispatch_again(Varlink *v);
+
+/* Get the currently processed incoming message */
+int varlink_get_current_parameters(Varlink *v, JsonVariant **ret);
+
/* Parsing incoming data via json_dispatch() and generate a nice error on parse errors */
int varlink_dispatch(Varlink *v, JsonVariant *parameters, const JsonDispatch table[], void *userdata);
int varlink_get_peer_uid(Varlink *v, uid_t *ret);
int varlink_get_peer_pid(Varlink *v, pid_t *ret);
+int varlink_get_peer_pidref(Varlink *v, PidRef *ret);
int varlink_set_relative_timeout(Varlink *v, usec_t usec);
/* This one we invented, and use for generically propagating system errors (errno) to clients */
#define VARLINK_ERROR_SYSTEM "io.systemd.System"
+/* This one we invented and is a weaker version of "org.varlink.service.PermissionDenied", and indicates that if user would allow interactive auth, we might allow access */
+#define VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED "io.systemd.InteractiveAuthenticationRequired"
+
/* These are errors defined in the Varlink spec */
#define VARLINK_ERROR_INTERFACE_NOT_FOUND "org.varlink.service.InterfaceNotFound"
#define VARLINK_ERROR_METHOD_NOT_FOUND "org.varlink.service.MethodNotFound"
assert_se(json_variant_has_type(w, json_variant_type(v)));
assert_se(json_variant_equal(v, w));
+ s = mfree(s);
+ r = json_variant_format(w, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ assert_se(r == -EPERM);
+ assert_se(!s);
+
+ s = mfree(s);
+ r = json_variant_format(w, JSON_FORMAT_PRETTY, &s);
+ assert_se(r >= 0);
+ assert_se(s);
+ assert_se((size_t) r == strlen(s));
+
s = mfree(s);
w = json_variant_unref(w);
assert_se(foobar.l == INT16_MIN);
}
+TEST(json_sensitive) {
+ _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL, *v = NULL;
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert_se(json_build(&a, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "bar", "baz", "foo", "qux", "baz"))) >= 0);
+ assert_se(json_build(&b, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "qux"))) >= 0);
+
+ json_variant_sensitive(a);
+
+ assert_se(json_variant_format(a, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+
+ r = json_variant_format(b, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ assert_se(r >= 0);
+ assert_se(s);
+ assert_se((size_t) r == strlen(s));
+ s = mfree(s);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ r = json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ assert_se(r >= 0);
+ assert_se(s);
+ assert_se((size_t) r == strlen(s));
+ s = mfree(s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ r = json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s);
+ assert_se(r >= 0);
+ assert_se(s);
+ assert_se((size_t) r == strlen(s));
+ s = mfree(s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR_VARIANT("a", a),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR_VARIANT("a", a),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR_VARIANT("a", a),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+ v = json_variant_unref(v);
+
+ assert_se(json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("b", b),
+ JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)),
+ JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")),
+ JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT),
+ JSON_BUILD_PAIR_VARIANT("a", a))) >= 0);
+ json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
+
+ assert_se(json_variant_format(v, JSON_FORMAT_REFUSE_SENSITIVE, &s) == -EPERM);
+ assert_se(!s);
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
}
}
+TEST(pid_get_start_time) {
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+
+ assert_se(pidref_set_self(&pidref) >= 0);
+
+ uint64_t start_time;
+ assert_se(pidref_get_start_time(&pidref, &start_time) >= 0);
+ log_info("our starttime: %" PRIu64, start_time);
+
+ _cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL;
+
+ assert_se(pidref_safe_fork("(stub)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS, &child) >= 0);
+
+ uint64_t start_time2;
+ assert_se(pidref_get_start_time(&child, &start_time2) >= 0);
+
+ log_info("child starttime: %" PRIu64, start_time2);
+
+ assert_se(start_time2 >= start_time);
+}
+
static int intro(void) {
log_show_color(true);
return EXIT_SUCCESS;
assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("i", "k", "l", "m", "d", "c", "a", "b"), haystack), "j"));
}
-TEST(strv_endswith) {
- assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("xxx", "yyy", "ldo", "zzz")), "ldo"));
- assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("xxx", "yyy", "zzz")), NULL));
- assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("waldo")), "waldo"));
- assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("w", "o", "ldo")), "o"));
- assert_se(streq_ptr(strv_endswith("waldo", STRV_MAKE("knurz", "", "waldo")), ""));
+TEST(endswith_strv) {
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("xxx", "yyy", "ldo", "zzz")), "ldo"));
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("xxx", "yyy", "zzz")), NULL));
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("waldo")), "waldo"));
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("w", "o", "ldo")), "o"));
+ assert_se(streq_ptr(endswith_strv("waldo", STRV_MAKE("knurz", "", "waldo")), ""));
}
DEFINE_TEST_MAIN(LOG_INFO);
assert(c);
free(c->zone);
- bus_verify_polkit_async_registry_free(c->polkit_registry);
+ hashmap_free(c->polkit_registry);
sd_bus_message_unref(c->cache);
sd_bus_slot_unref(c->slot_job_removed);
sd_bus_flush_close_unref(m->bus);
- bus_verify_polkit_async_registry_free(m->polkit_registry);
+ hashmap_free(m->polkit_registry);
return mfree(m);
}
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "generator.h"
+#include "parse-util.h"
#include "proc-cmdline.h"
#include "special.h"
#include "tpm2-util.h"
-#include "parse-util.h"
/* A small generator that enqueues tpm2.target as synchronization point if the TPM2 device hasn't shown up
* yet, but the firmware reports it to exist. This is supposed to deal with systems where the TPM2 driver
trap cleanup_test_user EXIT
}
-test_enable_debug() {
- mkdir -p /run/systemd/system/systemd-logind.service.d
- cat >/run/systemd/system/systemd-logind.service.d/debug.conf <<EOF
+test_write_dropin() {
+ systemctl edit --runtime --stdin systemd-logind.service --drop-in=debug.conf <<EOF
[Service]
Environment=SYSTEMD_LOG_LEVEL=debug
EOF
- systemctl daemon-reload
- systemctl stop systemd-logind.service
+
+ # We test "coldplug" (completely stop and start logind) here. So we need to preserve
+ # the fdstore, which might contain session leader pidfds. This is extremely rare use case
+ # and shall not be considered fully supported.
+ # See also: https://github.com/systemd/systemd/pull/30610#discussion_r1440507850
+ systemctl edit --runtime --stdin systemd-logind.service --drop-in=fdstore-preserve.conf <<EOF
+[Service]
+FileDescriptorStorePreserve=yes
+EOF
+
+ systemctl restart systemd-logind.service
}
testcase_properties() {
}
setup_test_user
-test_enable_debug
+test_write_dropin
run_testcases
touch /testok
pages = glob.glob(source_glob)
pages = (p for p in pages
if Path(p).name not in {
+ 'standard-conf.xml',
'systemd.directives.xml',
'systemd.index.xml',
'directives-template.xml'})
[Socket]
ListenStream=/run/systemd/io.systemd.Credentials
FileDescriptorName=varlink
-SocketMode=0600
+SocketMode=0666
Accept=yes
[Install]
DeviceAllow=char-tty rw
DeviceAllow=char-vcs rw
ExecStart={{LIBEXECDIR}}/systemd-logind
-FileDescriptorStoreMax=512
+FileDescriptorStoreMax=768
IPAddressDeny=any
LockPersonality=yes
MemoryDenyWriteExecute=yes