summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/busctl/busctl.c2
-rw-r--r--src/libsystemd/libsystemd.sym2
-rw-r--r--src/libsystemd/sd-bus/bus-internal.h2
-rw-r--r--src/libsystemd/sd-bus/sd-bus.c213
-rw-r--r--src/shared/bus-util.c15
-rw-r--r--src/stdio-bridge/stdio-bridge.c2
-rw-r--r--src/systemctl/systemctl.c2
-rw-r--r--src/systemd/sd-bus.h1
8 files changed, 217 insertions, 22 deletions
diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c
index 06a15ddd80..6f805e95a0 100644
--- a/src/busctl/busctl.c
+++ b/src/busctl/busctl.c
@@ -121,7 +121,7 @@ static int acquire_bus(bool set_monitor, sd_bus **ret) {
break;
case BUS_TRANSPORT_MACHINE:
- r = bus_set_address_system_machine(bus, arg_host);
+ r = bus_set_address_machine(bus, arg_user, arg_host);
break;
default:
diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym
index b03bcd952f..9e9e8fd372 100644
--- a/src/libsystemd/libsystemd.sym
+++ b/src/libsystemd/libsystemd.sym
@@ -739,6 +739,8 @@ global:
LIBSYSTEMD_248 {
global:
+ sd_bus_open_user_machine;
+
sd_event_source_set_ratelimit;
sd_event_source_get_ratelimit;
sd_event_source_is_ratelimited;
diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h
index 233a228315..82fa97fc5d 100644
--- a/src/libsystemd/sd-bus/bus-internal.h
+++ b/src/libsystemd/sd-bus/bus-internal.h
@@ -401,7 +401,7 @@ void bus_close_io_fds(sd_bus *b);
int bus_set_address_system(sd_bus *bus);
int bus_set_address_user(sd_bus *bus);
int bus_set_address_system_remote(sd_bus *b, const char *host);
-int bus_set_address_system_machine(sd_bus *b, const char *machine);
+int bus_set_address_machine(sd_bus *b, bool user, const char *machine);
int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error);
diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c
index da7827015a..e9bc19d96a 100644
--- a/src/libsystemd/sd-bus/sd-bus.c
+++ b/src/libsystemd/sd-bus/sd-bus.c
@@ -41,6 +41,7 @@
#include "process-util.h"
#include "string-util.h"
#include "strv.h"
+#include "user-util.h"
#define log_debug_bus_message(m) \
do { \
@@ -1514,44 +1515,228 @@ _public_ int sd_bus_open_system_remote(sd_bus **ret, const char *host) {
return 0;
}
-int bus_set_address_system_machine(sd_bus *b, const char *machine) {
- _cleanup_free_ char *e = NULL;
+int bus_set_address_machine(sd_bus *b, bool user, const char *machine) {
+ const char *rhs;
char *a;
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(hostname_is_valid(machine, VALID_HOSTNAME_DOT_HOST), -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)
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index fbda218b3b..58211ebd03 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -249,7 +249,12 @@ int bus_connect_user_systemd(sd_bus **_bus) {
return 0;
}
-int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **ret) {
+int bus_connect_transport(
+ BusTransport transport,
+ const char *host,
+ bool user,
+ sd_bus **ret) {
+
_cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL;
int r;
@@ -258,7 +263,7 @@ int bus_connect_transport(BusTransport transport, const char *host, bool user, s
assert(ret);
assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL);
- assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP);
+ assert_return(transport != BUS_TRANSPORT_REMOTE || !user, -EOPNOTSUPP);
switch (transport) {
@@ -279,7 +284,10 @@ int bus_connect_transport(BusTransport transport, const char *host, bool user, s
break;
case BUS_TRANSPORT_MACHINE:
- r = sd_bus_open_system_machine(&bus, host);
+ if (user)
+ r = sd_bus_open_user_machine(&bus, host);
+ else
+ r = sd_bus_open_system_machine(&bus, host);
break;
default:
@@ -293,7 +301,6 @@ int bus_connect_transport(BusTransport transport, const char *host, bool user, s
return r;
*ret = TAKE_PTR(bus);
-
return 0;
}
diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c
index 81d50717b2..1b7c3feaea 100644
--- a/src/stdio-bridge/stdio-bridge.c
+++ b/src/stdio-bridge/stdio-bridge.c
@@ -121,7 +121,7 @@ static int run(int argc, char *argv[]) {
return log_error_errno(r, "Failed to allocate bus: %m");
if (arg_transport == BUS_TRANSPORT_MACHINE)
- r = bus_set_address_system_machine(a, arg_bus_path);
+ r = bus_set_address_machine(a, false, arg_bus_path);
else
r = sd_bus_set_address(a, arg_bus_path);
if (r < 0)
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index d002d933ae..9a934badce 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -877,7 +877,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
assert_not_reached("Unhandled option");
}
- if (arg_transport != BUS_TRANSPORT_LOCAL && arg_scope != UNIT_FILE_SYSTEM)
+ if (arg_transport == BUS_TRANSPORT_REMOTE && arg_scope != UNIT_FILE_SYSTEM)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot access user instance remotely.");
diff --git a/src/systemd/sd-bus.h b/src/systemd/sd-bus.h
index 87fbcf366e..c51df2908d 100644
--- a/src/systemd/sd-bus.h
+++ b/src/systemd/sd-bus.h
@@ -135,6 +135,7 @@ int sd_bus_open(sd_bus **ret);
int sd_bus_open_with_description(sd_bus **ret, const char *description);
int sd_bus_open_user(sd_bus **ret);
int sd_bus_open_user_with_description(sd_bus **ret, const char *description);
+int sd_bus_open_user_machine(sd_bus **ret, const char *machine);
int sd_bus_open_system(sd_bus **ret);
int sd_bus_open_system_with_description(sd_bus **ret, const char *description);
int sd_bus_open_system_remote(sd_bus **ret, const char *host);