-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <endian.h>
#include <netdb.h>
-#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include "bus-socket.h"
#include "bus-track.h"
#include "bus-type.h"
-#include "bus-util.h"
#include "cgroup-util.h"
#include "def.h"
#include "errno-util.h"
#include "fd-util.h"
#include "hexdecoct.h"
#include "hostname-util.h"
+#include "io-util.h"
#include "macro.h"
#include "memory-util.h"
#include "missing_syscall.h"
#include "process-util.h"
#include "string-util.h"
#include "strv.h"
+#include "user-util.h"
#define log_debug_bus_message(m) \
do { \
.creds_mask = SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME,
.accept_fd = true,
.original_pid = getpid_cached(),
- .n_groups = (size_t) -1,
+ .n_groups = SIZE_MAX,
.close_on_exit = true,
};
int r;
struct addrinfo *result, hints = {
.ai_socktype = SOCK_STREAM,
- .ai_flags = AI_ADDRCONFIG,
};
assert(b);
return -EINVAL;
if (machine) {
- if (!streq(machine, ".host") && !machine_name_is_valid(machine))
+ if (!hostname_is_valid(machine, VALID_HOSTNAME_DOT_HOST))
return -EINVAL;
free_and_replace(b->machine, machine);
assert(b->input_fd >= 0);
assert(b->output_fd >= 0);
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *pi = NULL, *po = NULL;
+ (void) fd_get_path(b->input_fd, &pi);
+ (void) fd_get_path(b->output_fd, &po);
+ log_debug("sd-bus: starting bus%s%s on fds %d/%d (%s, %s)...",
+ b->description ? " " : "", strempty(b->description),
+ b->input_fd, b->output_fd,
+ pi ?: "???", po ?: "???");
+ }
+
r = fd_nonblock(b->input_fd, true);
if (r < 0)
return r;
e = secure_getenv("XDG_RUNTIME_DIR");
if (!e)
- return -ENOENT;
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
+ "sd-bus: $XDG_RUNTIME_DIR not set, cannot connect to user bus.");
ee = bus_address_escape(e);
if (!ee)
}
if (!in_charset(p, "0123456789") || *p == '\0') {
- if (!machine_name_is_valid(p) || got_forward_slash)
+ if (!hostname_is_valid(p, 0) || got_forward_slash)
return -EINVAL;
m = TAKE_PTR(p);
interpret_port_as_machine_old_syntax:
/* Let's make sure this is not a port of some kind,
* and is a valid machine name. */
- if (!in_charset(m, "0123456789") && machine_name_is_valid(m))
+ if (!in_charset(m, "0123456789") && hostname_is_valid(m, 0))
c = strjoina(",argv", p ? "7" : "5", "=--machine=", m);
}
return 0;
}
-int bus_set_address_system_machine(sd_bus *b, const char *machine) {
- _cleanup_free_ char *e = NULL;
- char *a;
+int bus_set_address_machine(sd_bus *b, bool user, const char *machine) {
+ _cleanup_free_ char *a = NULL;
+ const char *rhs;
assert(b);
assert(machine);
- e = bus_address_escape(machine);
- if (!e)
- return -ENOMEM;
+ rhs = strchr(machine, '@');
+ if (rhs || user) {
+ _cleanup_free_ char *u = NULL, *eu = NULL, *erhs = NULL;
+
+ /* If there's an "@" in the container specification, we'll connect as a user specified at its
+ * left hand side, which is useful in combination with user=true. This isn't as trivial as it
+ * might sound: it's not sufficient to enter the container and connect to some socket there,
+ * since the --user socket path depends on $XDG_RUNTIME_DIR which is set via PAM. Thus, to be
+ * able to connect, we need to have a PAM session. Our way out? We use systemd-run to get
+ * into the container and acquire a PAM session there, and then invoke systemd-stdio-bridge
+ * in it, which propagates the bus transport to us.*/
+
+ if (rhs) {
+ if (rhs > machine)
+ u = strndup(machine, rhs - machine);
+ else
+ u = getusername_malloc(); /* Empty user name, let's use the local one */
+ if (!u)
+ return -ENOMEM;
- a = strjoin("x-machine-unix:machine=", e);
- if (!a)
- return -ENOMEM;
+ eu = bus_address_escape(u);
+ if (!eu)
+ return -ENOMEM;
+
+ rhs++;
+ } else {
+ /* No "@" specified but we shall connect to the user instance? Then assume root (and
+ * not a user named identically to the calling one). This means:
+ *
+ * --machine=foobar --user → connect to user bus of root user in container "foobar"
+ * --machine=@foobar --user → connect to user bus of user named like the calling user in container "foobar"
+ *
+ * Why? so that behaviour for "--machine=foobar --system" is roughly similar to
+ * "--machine=foobar --user": both times we unconditionally connect as root user
+ * regardless what the calling user is. */
+
+ rhs = machine;
+ }
+
+ if (!isempty(rhs)) {
+ erhs = bus_address_escape(rhs);
+ if (!erhs)
+ return -ENOMEM;
+ }
+
+ /* systemd-run -M… -PGq --wait -pUser=… -pPAMName=login systemd-stdio-bridge */
+
+ a = strjoin("unixexec:path=systemd-run,"
+ "argv1=-M", erhs ?: ".host", ","
+ "argv2=-PGq,"
+ "argv3=--wait,"
+ "argv4=-pUser%3d", eu ?: "root", ",",
+ "argv5=-pPAMName%3dlogin,"
+ "argv6=systemd-stdio-bridge");
+ if (!a)
+ return -ENOMEM;
+
+ if (user) {
+ char *k;
+
+ /* Ideally we'd use the "--user" switch to systemd-stdio-bridge here, but it's only
+ * available in recent systemd versions. Using the "-p" switch with the explicit path
+ * is a working alternative, and is compatible with older versions, hence that's what
+ * we use here. */
+
+ k = strjoin(a, ",argv7=-punix:path%3d%24%7bXDG_RUNTIME_DIR%7d/bus");
+ if (!k)
+ return -ENOMEM;
+
+ free_and_replace(a, k);
+ }
+ } else {
+ _cleanup_free_ char *e = NULL;
+
+ /* Just a container name, we can go the simple way, and just join the container, and connect
+ * to the well-known path of the system bus there. */
+
+ e = bus_address_escape(machine);
+ if (!e)
+ return -ENOMEM;
+
+ a = strjoin("x-machine-unix:machine=", e);
+ if (!a)
+ return -ENOMEM;
+ }
return free_and_replace(b->address, a);
}
-_public_ int sd_bus_open_system_machine(sd_bus **ret, const char *machine) {
+static int user_and_machine_valid(const char *user_and_machine) {
+ const char *h;
+
+ /* Checks if a container specification in the form "user@container" or just "container" is valid.
+ *
+ * If the "@" syntax is used we'll allow either the "user" or the "container" part to be omitted, but
+ * not both. */
+
+ h = strchr(user_and_machine, '@');
+ if (!h)
+ h = user_and_machine;
+ else {
+ _cleanup_free_ char *user = NULL;
+
+ user = strndup(user_and_machine, h - user_and_machine);
+ if (!user)
+ return -ENOMEM;
+
+ if (!isempty(user) && !valid_user_group_name(user, VALID_USER_RELAX))
+ return false;
+
+ h++;
+
+ if (isempty(h))
+ return !isempty(user);
+ }
+
+ return hostname_is_valid(h, VALID_HOSTNAME_DOT_HOST);
+}
+
+static int user_and_machine_equivalent(const char *user_and_machine) {
+ _cleanup_free_ char *un = NULL;
+ const char *f;
+
+ /* Returns true if the specified user+machine name are actually equivalent to our own identity and
+ * our own host. If so we can shortcut things. Why bother? Because that way we don't have to fork
+ * off short-lived worker processes that are then unavailable for authentication and logging in the
+ * peer. Moreover joining a namespace requires privileges. If we are in the right namespace anyway,
+ * we can avoid permission problems thus. */
+
+ assert(user_and_machine);
+
+ /* Omitting the user name means that we shall use the same user name as we run as locally, which
+ * means we'll end up on the same host, let's shortcut */
+ if (streq(user_and_machine, "@.host"))
+ return true;
+
+ /* Otherwise, if we are root, then we can also allow the ".host" syntax, as that's the user this
+ * would connect to. */
+ if (geteuid() == 0 && STR_IN_SET(user_and_machine, ".host", "root@.host"))
+ return true;
+
+ /* Otherwise, we have to figure our user name, and compare things with that. */
+ un = getusername_malloc();
+ if (!un)
+ return -ENOMEM;
+
+ f = startswith(user_and_machine, un);
+ if (!f)
+ return false;
+
+ return STR_IN_SET(f, "@", "@.host");
+}
+
+_public_ int sd_bus_open_system_machine(sd_bus **ret, const char *user_and_machine) {
_cleanup_(bus_freep) sd_bus *b = NULL;
int r;
- assert_return(machine, -EINVAL);
+ assert_return(user_and_machine, -EINVAL);
assert_return(ret, -EINVAL);
- assert_return(streq(machine, ".host") || machine_name_is_valid(machine), -EINVAL);
+
+ if (user_and_machine_equivalent(user_and_machine))
+ return sd_bus_open_system(ret);
+
+ r = user_and_machine_valid(user_and_machine);
+ if (r < 0)
+ return r;
+
+ assert_return(r > 0, -EINVAL);
r = sd_bus_new(&b);
if (r < 0)
return r;
- r = bus_set_address_system_machine(b, machine);
+ r = bus_set_address_machine(b, false, user_and_machine);
if (r < 0)
return r;
b->bus_client = true;
- b->trusted = false;
b->is_system = true;
- b->is_local = false;
+
+ r = sd_bus_start(b);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(b);
+ return 0;
+}
+
+_public_ int sd_bus_open_user_machine(sd_bus **ret, const char *user_and_machine) {
+ _cleanup_(bus_freep) sd_bus *b = NULL;
+ int r;
+
+ assert_return(user_and_machine, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ /* Shortcut things if we'd end up on this host and as the same user. */
+ if (user_and_machine_equivalent(user_and_machine))
+ return sd_bus_open_user(ret);
+
+ r = user_and_machine_valid(user_and_machine);
+ if (r < 0)
+ return r;
+
+ assert_return(r > 0, -EINVAL);
+
+ r = sd_bus_new(&b);
+ if (r < 0)
+ return r;
+
+ r = bus_set_address_machine(b, true, user_and_machine);
+ if (r < 0)
+ return r;
+
+ b->bus_client = true;
+ b->trusted = true;
r = sd_bus_start(b);
if (r < 0)
* hence let's fill something in for synthetic messages. Since
* synthetic messages might have a fake sender and we don't
* want to interfere with the real sender's serial numbers we
- * pick a fixed, artificial one. We use (uint32_t) -1 rather
- * than (uint64_t) -1 since dbus1 only had 32bit identifiers,
+ * pick a fixed, artificial one. We use UINT32_MAX rather
+ * than UINT64_MAX since dbus1 only had 32bit identifiers,
* even though kdbus can do 64bit. */
return sd_bus_message_seal(m, 0xFFFFFFFFULL, 0);
}
static usec_t calc_elapse(sd_bus *bus, uint64_t usec) {
assert(bus);
- if (usec == (uint64_t) -1)
+ assert_cc(sizeof(usec_t) == sizeof(uint64_t));
+
+ if (usec == USEC_INFINITY)
return 0;
/* We start all timeouts the instant we enter BUS_HELLO/BUS_RUNNING state, so that the don't run in parallel
if (IN_SET(bus->state, BUS_WATCH_BIND, BUS_OPENING, BUS_AUTHENTICATING))
return usec;
else
- return now(CLOCK_MONOTONIC) + usec;
+ return usec_add(now(CLOCK_MONOTONIC), usec);
}
static int timeout_compare(const void *a, const void *b) {
assert(bus);
- if (IN_SET(bus->state, BUS_UNSET, BUS_CLOSED, BUS_CLOSING))
- return -ENOTCONN;
if (bus->state == BUS_RUNNING)
return 1;
for (;;) {
+ if (IN_SET(bus->state, BUS_UNSET, BUS_CLOSED, BUS_CLOSING))
+ return -ENOTCONN;
+
r = sd_bus_process(bus, NULL);
if (r < 0)
return r;
if (r > 0)
continue;
- r = sd_bus_wait(bus, (uint64_t) -1);
+ r = sd_bus_wait(bus, UINT64_MAX);
if (r < 0)
return r;
}
left = timeout - n;
} else
- left = (uint64_t) -1;
+ left = UINT64_MAX;
r = bus_poll(bus, true, left);
if (r < 0)
c = prioq_peek(bus->reply_callbacks_prioq);
if (!c) {
- *timeout_usec = (uint64_t) -1;
+ *timeout_usec = UINT64_MAX;
return 0;
}
if (c->timeout_usec == 0) {
- *timeout_usec = (uint64_t) -1;
+ *timeout_usec = UINT64_MAX;
return 0;
}
case BUS_WATCH_BIND:
case BUS_OPENING:
- *timeout_usec = (uint64_t) -1;
+ *timeout_usec = UINT64_MAX;
return 0;
default:
static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) {
struct pollfd p[2] = {};
- int r, n;
- struct timespec ts;
usec_t m = USEC_INFINITY;
+ int r, n;
assert(bus);
}
}
- if (timeout_usec != (uint64_t) -1 && (m == USEC_INFINITY || timeout_usec < m))
+ if (timeout_usec != UINT64_MAX && (m == USEC_INFINITY || timeout_usec < m))
m = timeout_usec;
- r = ppoll(p, n, m == USEC_INFINITY ? NULL : timespec_store(&ts, m), NULL);
- if (r < 0)
- return -errno;
- if (r == 0)
- return 0;
-
- if (p[0].revents & POLLNVAL)
- return -EBADF;
- if (n >= 2 && (p[1].revents & POLLNVAL))
- return -EBADF;
+ r = ppoll_usec(p, n, m);
+ if (r <= 0)
+ return r;
return 1;
}
if (bus->wqueue_size <= 0)
return 0;
- r = bus_poll(bus, false, (uint64_t) -1);
+ r = bus_poll(bus, false, UINT64_MAX);
if (r < 0)
return r;
}