]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/libsystemd/sd-bus/sd-bus.c
tree-wide: use UINT64_MAX or friends
[thirdparty/systemd.git] / src / libsystemd / sd-bus / sd-bus.c
index b8d4dc8d959f631aedee917e3d13c2ddcd29512f..e719c74370f3f081e54f9acb9eca48c6a43b5f06 100644 (file)
@@ -2,7 +2,6 @@
 
 #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"
@@ -41,6 +40,7 @@
 #include "process-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "user-util.h"
 
 #define log_debug_bus_message(m)                                         \
         do {                                                             \
@@ -247,7 +247,7 @@ _public_ int sd_bus_new(sd_bus **ret) {
                 .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,
         };
 
@@ -973,7 +973,7 @@ static int parse_container_unix_address(sd_bus *b, const char **p, char **guid)
                 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);
@@ -1448,7 +1448,7 @@ int bus_set_address_system_remote(sd_bus *b, const char *host) {
                 }
 
                 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);
@@ -1463,7 +1463,7 @@ int bus_set_address_system_remote(sd_bus *b, const char *host) {
 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);
         }
 
@@ -1514,44 +1514,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;
-        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)
@@ -1780,8 +1964,8 @@ int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m) {
          * 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);
 }
@@ -2021,7 +2205,9 @@ _public_ int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destinat
 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
@@ -2031,7 +2217,7 @@ static usec_t calc_elapse(sd_bus *bus, uint64_t usec) {
         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) {
@@ -2146,7 +2332,7 @@ int bus_ensure_running(sd_bus *bus) {
                 if (r > 0)
                         continue;
 
-                r = sd_bus_wait(bus, (uint64_t) -1);
+                r = sd_bus_wait(bus, UINT64_MAX);
                 if (r < 0)
                         return r;
         }
@@ -2274,7 +2460,7 @@ _public_ int sd_bus_call(
 
                         left = timeout - n;
                 } else
-                        left = (uint64_t) -1;
+                        left = UINT64_MAX;
 
                 r = bus_poll(bus, true, left);
                 if (r < 0)
@@ -2394,12 +2580,12 @@ _public_ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) {
 
                 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;
                 }
 
@@ -2412,7 +2598,7 @@ _public_ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) {
 
         case BUS_WATCH_BIND:
         case BUS_OPENING:
-                *timeout_usec = (uint64_t) -1;
+                *timeout_usec = UINT64_MAX;
                 return 0;
 
         default:
@@ -3071,9 +3257,8 @@ _public_ int sd_bus_process_priority(sd_bus *bus, int64_t priority, sd_bus_messa
 
 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);
 
@@ -3125,19 +3310,12 @@ static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) {
                 }
         }
 
-        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;
 }
@@ -3198,7 +3376,7 @@ _public_ int sd_bus_flush(sd_bus *bus) {
                 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;
         }