chiark / gitweb /
systemctl: support remote and privileged systemctl access via SSH and pkexec
authorLennart Poettering <lennart@poettering.net>
Sat, 12 Mar 2011 00:03:13 +0000 (01:03 +0100)
committerLennart Poettering <lennart@poettering.net>
Sat, 12 Mar 2011 00:07:17 +0000 (01:07 +0100)
This adds support for executing systemctl operations remotely or as
privileged user while still running systemctl itself unprivileged and
locally.

This currently requires a D-Bus patch to work properly.

https://bugs.freedesktop.org/show_bug.cgi?id=35230

.gitignore
Makefile.am
TODO
man/systemctl.xml
src/bridge.c [new file with mode: 0644]
src/dbus-common.c
src/dbus-common.h
src/org.freedesktop.systemd1.policy
src/systemctl.c

index 0584d90..ffc602a 100644 (file)
@@ -1,3 +1,4 @@
+systemd-stdio-bridge
 systemd-machine-id-setup
 systemd-detect-virt
 systemd-sysctl
index f867624..7120636 100644 (file)
@@ -58,6 +58,7 @@ AM_CPPFLAGS = \
        -DSYSTEMD_SHUTDOWN_BINARY_PATH=\"$(rootlibexecdir)/systemd-shutdown\" \
        -DSYSTEMCTL_BINARY_PATH=\"$(rootbindir)/systemctl\" \
        -DSYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH=\"$(rootbindir)/systemd-tty-ask-password-agent\" \
+        -DSYSTEMD_STDIO_BRIDGE_BINARY_PATH=\"$(bindir)/systemd-stdio-bridge\" \
        -DRUNTIME_DIR=\"$(localstatedir)/run\" \
        -DRANDOM_SEED=\"$(localstatedir)/lib/random-seed\" \
        -DSYSTEMD_CRYPTSETUP_PATH=\"$(rootlibexecdir)/systemd-cryptsetup\" \
@@ -111,7 +112,8 @@ rootsbin_PROGRAMS = \
        systemd-machine-id-setup
 
 bin_PROGRAMS = \
-       systemd-cgls
+       systemd-cgls \
+       systemd-stdio-bridge
 
 if HAVE_GTK
 bin_PROGRAMS += \
@@ -980,6 +982,12 @@ systemd_cgls_CFLAGS = \
 systemd_cgls_LDADD = \
        libsystemd-basic.la
 
+systemd_stdio_bridge_SOURCES = \
+       src/bridge.c
+
+systemd_stdio_bridge_LDADD = \
+       libsystemd-basic.la
+
 systemadm_SOURCES = \
        src/systemadm.vala \
        src/systemd-interfaces.vala
diff --git a/TODO b/TODO
index 6404719..a36ca5e 100644 (file)
--- a/TODO
+++ b/TODO
@@ -26,6 +26,8 @@ Features:
 
 * optionally create watched directories in .path units
 
+* Support --test based on current system state
+
 * consider services with no [Install] section and stored in /lib enabled by "systemctl is-enabled"
 
 * consider services with any kind of link in /etc/systemd/system enabled
index d37a7d9..d6e0a51 100644 (file)
                                 file that shall be
                                 disabled.</para></listitem>
                         </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-H</option></term>
+                                <term><option>--host</option></term>
+
+                                <listitem><para>Execute operation
+                                remotely. Specifiy a hostname, or
+                                username and hostname seperated by @,
+                                to connect to. This will use SSH to
+                                talk to the remote systemd
+                                instance.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>-P</option></term>
+                                <term><option>--privileged</option></term>
+
+                                <listitem><para>Acquire privileges via
+                                PolicyKit before executing the
+                                operation.</para></listitem>
+                        </varlistentry>
                 </variablelist>
 
                 <para>The following commands are understood:</para>
diff --git a/src/bridge.c b/src/bridge.c
new file mode 100644 (file)
index 0000000..5ee058a
--- /dev/null
@@ -0,0 +1,367 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/epoll.h>
+#include <stddef.h>
+
+#include "log.h"
+#include "util.h"
+#include "socket-util.h"
+
+#define BUFFER_SIZE (64*1024)
+#define EXTRA_SIZE 16
+
+static bool initial_nul = false;
+static bool auth_over = false;
+
+static void format_uid(char *buf, size_t l) {
+        char text[20 + 1]; /* enough space for a 64bit integer plus NUL */
+        unsigned j;
+
+        assert(l > 0);
+
+        snprintf(text, sizeof(text)-1, "%llu", (unsigned long long) geteuid());
+        text[sizeof(text)-1] = 0;
+
+        memset(buf, 0, l);
+
+        for (j = 0; text[j] && j*2+2 < l; j++) {
+                buf[j*2]   = hexchar(text[j] >> 4);
+                buf[j*2+1] = hexchar(text[j] & 0xF);
+        }
+
+        buf[j*2] = 0;
+}
+
+static size_t patch_in_line(char *line, size_t l, size_t left) {
+        size_t r;
+
+        if (line[0] == 0 && !initial_nul) {
+                initial_nul = true;
+                line += 1;
+                l -= 1;
+                r = 1;
+        } else
+                r = 0;
+
+        if (l == 5 && strncmp(line, "BEGIN", 5) == 0) {
+                r += l;
+                auth_over = true;
+
+        } else if (l == 17 && strncmp(line, "NEGOTIATE_UNIX_FD", 17) == 0) {
+                memmove(line + 13, line + 17, left);
+                memcpy(line, "NEGOTIATE_NOP", 13);
+                r += 13;
+
+        } else if (l >= 14 && strncmp(line, "AUTH EXTERNAL ", 14) == 0) {
+                char uid[20*2 + 1];
+                size_t len;
+
+                format_uid(uid, sizeof(uid));
+                len = strlen(uid);
+                assert(len <= EXTRA_SIZE);
+
+                memmove(line + 14 + len, line + l, left);
+                memcpy(line + 14, uid, len);
+
+                r += 14 + len;
+        } else
+                r += l;
+
+        return r;
+}
+
+static size_t patch_in_buffer(char* in_buffer, size_t *in_buffer_full) {
+        size_t i, good = 0;
+
+        if (*in_buffer_full <= 0)
+                return *in_buffer_full;
+
+        /* If authentication is done, we don't touch anything anymore */
+        if (auth_over)
+                return *in_buffer_full;
+
+        if (*in_buffer_full < 2)
+                return 0;
+
+        for (i = 0; i <= *in_buffer_full - 2; i ++) {
+
+                /* Fully lines can be send on */
+                if (in_buffer[i] == '\r' && in_buffer[i+1] == '\n') {
+                        if (i > good) {
+                                size_t old_length, new_length;
+
+                                old_length = i - good;
+                                new_length = patch_in_line(in_buffer+good, old_length, *in_buffer_full - i);
+                                *in_buffer_full = *in_buffer_full + new_length - old_length;
+
+                                good += new_length + 2;
+
+                        } else
+                                good = i+2;
+                }
+
+                if (auth_over)
+                        break;
+        }
+
+        return good;
+}
+
+int main(int argc, char *argv[]) {
+        int r = EXIT_FAILURE, fd = -1, ep = -1;
+        union sockaddr_union sa;
+        char in_buffer[BUFFER_SIZE+EXTRA_SIZE], out_buffer[BUFFER_SIZE+EXTRA_SIZE];
+        size_t in_buffer_full = 0, out_buffer_full = 0;
+        struct epoll_event stdin_ev, stdout_ev, fd_ev;
+        bool stdin_readable = false, stdout_writable = false, fd_readable = false, fd_writable = false;
+        bool stdin_rhup = false, stdout_whup = false, fd_rhup = false, fd_whup = false;
+
+        if (argc > 1) {
+                log_error("This program takes no argument.");
+                return EXIT_FAILURE;
+        }
+
+        log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
+        log_parse_environment();
+        log_open();
+
+        if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
+                log_error("Failed to create socket: %s", strerror(errno));
+                goto finish;
+        }
+
+        zero(sa);
+        sa.un.sun_family = AF_UNIX;
+        strncpy(sa.un.sun_path, "/var/run/dbus/system_bus_socket", sizeof(sa.un.sun_path));
+
+        if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
+                log_error("Failed to connect: %m");
+                goto finish;
+        }
+
+        fd_nonblock(STDIN_FILENO, 1);
+        fd_nonblock(STDOUT_FILENO, 1);
+
+        if ((ep = epoll_create1(EPOLL_CLOEXEC)) < 0) {
+                log_error("Failed to create epoll: %m");
+                goto finish;
+        }
+
+        zero(stdin_ev);
+        stdin_ev.events = EPOLLIN|EPOLLET;
+        stdin_ev.data.fd = STDIN_FILENO;
+
+        zero(stdout_ev);
+        stdout_ev.events = EPOLLOUT|EPOLLET;
+        stdout_ev.data.fd = STDOUT_FILENO;
+
+        zero(fd_ev);
+        fd_ev.events = EPOLLIN|EPOLLOUT|EPOLLET;
+        fd_ev.data.fd = fd;
+
+        if (epoll_ctl(ep, EPOLL_CTL_ADD, STDIN_FILENO, &stdin_ev) < 0 ||
+            epoll_ctl(ep, EPOLL_CTL_ADD, STDOUT_FILENO, &stdout_ev) < 0 ||
+            epoll_ctl(ep, EPOLL_CTL_ADD, fd, &fd_ev) < 0) {
+                log_error("Failed to regiser fds in epoll: %m");
+                goto finish;
+        }
+
+        do {
+                struct epoll_event ev[16];
+                ssize_t k;
+                int i, nfds;
+
+                if ((nfds = epoll_wait(ep, ev, ELEMENTSOF(ev), -1)) < 0) {
+
+                        if (errno == EINTR || errno == EAGAIN)
+                                continue;
+
+                        log_error("epoll_wait(): %m");
+                        goto finish;
+                }
+
+                assert(nfds >= 1);
+
+                for (i = 0; i < nfds; i++) {
+                        if (ev[i].data.fd == STDIN_FILENO) {
+
+                                if (!stdin_rhup && (ev[i].events & (EPOLLHUP|EPOLLIN)))
+                                        stdin_readable = true;
+
+                        } else if (ev[i].data.fd == STDOUT_FILENO) {
+
+                                if (ev[i].events & EPOLLHUP) {
+                                        stdout_writable = false;
+                                        stdout_whup = true;
+                                }
+
+                                if (!stdout_whup && (ev[i].events & EPOLLOUT))
+                                        stdout_writable = true;
+
+                        } else if (ev[i].data.fd == fd) {
+
+                                if (ev[i].events & EPOLLHUP) {
+                                        fd_writable = false;
+                                        fd_whup = true;
+                                }
+
+                                if (!fd_rhup && (ev[i].events & (EPOLLHUP|EPOLLIN)))
+                                        fd_readable = true;
+
+                                if (!fd_whup && (ev[i].events & EPOLLOUT))
+                                        fd_writable = true;
+                        }
+                }
+
+                while ((stdin_readable && in_buffer_full <= 0) ||
+                       (fd_writable && patch_in_buffer(in_buffer, &in_buffer_full) > 0) ||
+                       (fd_readable && out_buffer_full <= 0) ||
+                       (stdout_writable && out_buffer_full > 0)) {
+
+                        size_t in_buffer_good = 0;
+
+                        if (stdin_readable && in_buffer_full < BUFFER_SIZE) {
+
+                                if ((k = read(STDIN_FILENO, in_buffer + in_buffer_full, BUFFER_SIZE - in_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN)
+                                                stdin_readable = false;
+                                        else if (errno == EPIPE || errno == ECONNRESET)
+                                                k = 0;
+                                        else {
+                                                log_error("read(): %m");
+                                                goto finish;
+                                        }
+                                } else
+                                        in_buffer_full += (size_t) k;
+
+                                if (k == 0) {
+                                        stdin_rhup = true;
+                                        stdin_readable = false;
+                                        shutdown(STDIN_FILENO, SHUT_RD);
+                                        close_nointr_nofail(STDIN_FILENO);
+                                }
+                        }
+
+                        in_buffer_good = patch_in_buffer(in_buffer, &in_buffer_full);
+
+                        if (fd_writable && in_buffer_good > 0) {
+
+                                if ((k = write(fd, in_buffer, in_buffer_good)) < 0) {
+
+                                        if (errno == EAGAIN)
+                                                fd_writable = false;
+                                        else if (errno == EPIPE || errno == ECONNRESET) {
+                                                fd_whup = true;
+                                                fd_writable = false;
+                                                shutdown(fd, SHUT_WR);
+                                        } else {
+                                                log_error("write(): %m");
+                                                goto finish;
+                                        }
+
+                                } else {
+                                        assert(in_buffer_full >= (size_t) k);
+                                        memmove(in_buffer, in_buffer + k, in_buffer_full - k);
+                                        in_buffer_full -= k;
+                                }
+                        }
+
+                        if (fd_readable && out_buffer_full < BUFFER_SIZE) {
+
+                                if ((k = read(fd, out_buffer + out_buffer_full, BUFFER_SIZE - out_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN)
+                                                fd_readable = false;
+                                        else if (errno == EPIPE || errno == ECONNRESET)
+                                                k = 0;
+                                        else {
+                                                log_error("read(): %m");
+                                                goto finish;
+                                        }
+                                }  else
+                                        out_buffer_full += (size_t) k;
+
+                                if (k == 0) {
+                                        fd_rhup = true;
+                                        fd_readable = false;
+                                        shutdown(fd, SHUT_RD);
+                                }
+                        }
+
+                        if (stdout_writable && out_buffer_full > 0) {
+
+                                if ((k = write(STDOUT_FILENO, out_buffer, out_buffer_full)) < 0) {
+
+                                        if (errno == EAGAIN)
+                                                stdout_writable = false;
+                                        else if (errno == EPIPE || errno == ECONNRESET) {
+                                                stdout_whup = true;
+                                                stdout_writable = false;
+                                                shutdown(STDOUT_FILENO, SHUT_WR);
+                                                close_nointr(STDOUT_FILENO);
+                                        } else {
+                                                log_error("write(): %m");
+                                                goto finish;
+                                        }
+
+                                } else {
+                                        assert(out_buffer_full >= (size_t) k);
+                                        memmove(out_buffer, out_buffer + k, out_buffer_full - k);
+                                        out_buffer_full -= k;
+                                }
+                        }
+                }
+
+                if (stdin_rhup && in_buffer_full <= 0 && !fd_whup) {
+                        fd_whup = true;
+                        fd_writable = false;
+                        shutdown(fd, SHUT_WR);
+                }
+
+                if (fd_rhup && out_buffer_full <= 0 && !stdout_whup) {
+                        stdout_whup = true;
+                        stdout_writable = false;
+                        shutdown(STDOUT_FILENO, SHUT_WR);
+                        close_nointr(STDOUT_FILENO);
+                }
+
+        } while (!stdout_whup || !fd_whup);
+
+        r = EXIT_SUCCESS;
+
+finish:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        if (ep >= 0)
+                close_nointr_nofail(ep);
+
+        return r;
+}
index 809ea0f..25b718e 100644 (file)
@@ -23,6 +23,8 @@
 #include <sys/socket.h>
 #include <errno.h>
 #include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <dbus/dbus.h>
 
 #include "log.h"
@@ -55,59 +57,63 @@ int bus_check_peercred(DBusConnection *c) {
         return 1;
 }
 
-int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private, DBusError *error) {
-        DBusConnection *bus;
+#define TIMEOUT_USEC (60*USEC_PER_SEC)
 
-        assert(_bus);
+static int sync_auth(DBusConnection *bus, DBusError *error) {
+        usec_t begin, tstamp;
 
-#define TIMEOUT_USEC (60*USEC_PER_SEC)
+        assert(bus);
 
-        /* If we are root, then let's not go via the bus */
-        if (geteuid() == 0 && t == DBUS_BUS_SYSTEM) {
-                usec_t begin, tstamp;
+        /* This complexity should probably move into D-Bus itself:
+         *
+         * https://bugs.freedesktop.org/show_bug.cgi?id=35189 */
 
-                if (!(bus = dbus_connection_open_private("unix:abstract=/org/freedesktop/systemd1/private", error)))
-                        return -EIO;
+        begin = tstamp = now(CLOCK_MONOTONIC);
+        for (;;) {
 
-                if (bus_check_peercred(bus) < 0) {
-                        dbus_connection_close(bus);
-                        dbus_connection_unref(bus);
+                if (tstamp > begin + TIMEOUT_USEC)
+                        break;
 
-                        dbus_set_error_const(error, DBUS_ERROR_ACCESS_DENIED, "Failed to verify owner of bus.");
-                        return -EACCES;
-                }
+                if (dbus_connection_get_is_authenticated(bus))
+                        break;
 
-                /* This complexity should probably move into D-Bus itself:
-                 *
-                 * https://bugs.freedesktop.org/show_bug.cgi?id=35189 */
-                begin = tstamp = now(CLOCK_MONOTONIC);
-                for (;;) {
+                if (!dbus_connection_read_write_dispatch(bus, ((begin + TIMEOUT_USEC - tstamp) + USEC_PER_MSEC - 1) / USEC_PER_MSEC))
+                        break;
 
-                        if (tstamp > begin + TIMEOUT_USEC)
-                                break;
+                tstamp = now(CLOCK_MONOTONIC);
+        }
 
-                        if (dbus_connection_get_is_authenticated(bus))
-                                break;
+        if (!dbus_connection_get_is_connected(bus)) {
+                dbus_set_error_const(error, DBUS_ERROR_NO_SERVER, "Connection terminated during authentication.");
+                return -ECONNREFUSED;
+        }
 
-                        if (!dbus_connection_read_write_dispatch(bus, ((begin + TIMEOUT_USEC - tstamp) + USEC_PER_MSEC - 1) / USEC_PER_MSEC))
-                                break;
+        if (!dbus_connection_get_is_authenticated(bus)) {
+                dbus_set_error_const(error, DBUS_ERROR_TIMEOUT, "Failed to authenticate in time.");
+                return -EACCES;
+        }
 
-                        tstamp = now(CLOCK_MONOTONIC);
-                }
+        return 0;
+}
 
-                if (!dbus_connection_get_is_connected(bus)) {
-                        dbus_connection_close(bus);
-                        dbus_connection_unref(bus);
+int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private, DBusError *error) {
+        DBusConnection *bus;
+        int r;
 
-                        dbus_set_error_const(error, DBUS_ERROR_NO_SERVER, "Connection terminated during authentication.");
-                        return -ECONNREFUSED;
-                }
+        assert(_bus);
 
-                if (!dbus_connection_get_is_authenticated(bus)) {
+        /* If we are root, then let's not go via the bus */
+        if (geteuid() == 0 && t == DBUS_BUS_SYSTEM) {
+                if (!(bus = dbus_connection_open_private("unix:abstract=/org/freedesktop/systemd1/private", error)))
+                        return -EIO;
+
+                dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+                if (bus_check_peercred(bus) < 0) {
                         dbus_connection_close(bus);
                         dbus_connection_unref(bus);
 
-                        dbus_set_error_const(error, DBUS_ERROR_TIMEOUT, "Failed to authenticate in time.");
+                        dbus_set_error_const(error, DBUS_ERROR_ACCESS_DENIED, "Failed to verify owner of bus.");
                         return -EACCES;
                 }
 
@@ -118,12 +124,93 @@ int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private, DBusError *
                 if (!(bus = dbus_bus_get_private(t, error)))
                         return -EIO;
 
+                dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
                 if (private)
                         *private = false;
         }
 
+        if ((r = sync_auth(bus, error)) < 0) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
+        *_bus = bus;
+        return 0;
+}
+
+int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error) {
+        DBusConnection *bus;
+        char *p = NULL;
+        int r;
+
+        assert(_bus);
+        assert(user || host);
+
+        if (user && host)
+                asprintf(&p, "exec:path=ssh,argv1=-xT,argv2=%s@%s,argv3=systemd-stdio-bridge", user, host);
+        else if (user)
+                asprintf(&p, "exec:path=ssh,argv1=-xT,argv2=%s@localhost,argv3=systemd-stdio-bridge", user);
+        else if (host)
+                asprintf(&p, "exec:path=ssh,argv1=-xT,argv2=%s,argv3=systemd-stdio-bridge", host);
+
+        if (!p) {
+                dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
+                return -ENOMEM;
+        }
+
+        bus = dbus_connection_open_private(p, error);
+        free(p);
+
+        if (!bus)
+                return -EIO;
+
+        dbus_connection_set_exit_on_disconnect(bus, FALSE);
+
+        if ((r = sync_auth(bus, error)) < 0) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
+        if (!dbus_bus_register(bus, error)) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
+        *_bus = bus;
+        return 0;
+}
+
+int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error) {
+        DBusConnection *bus;
+        int r;
+
+        assert(_bus);
+
+        /* Don't bother with PolicyKit if we are root */
+        if (geteuid() == 0)
+                return bus_connect(DBUS_BUS_SYSTEM, _bus, NULL, error);
+
+        if (!(bus = dbus_connection_open_private("exec:path=pkexec,argv1=" SYSTEMD_STDIO_BRIDGE_BINARY_PATH, error)))
+                return -EIO;
+
         dbus_connection_set_exit_on_disconnect(bus, FALSE);
 
+        if ((r = sync_auth(bus, error)) < 0) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
+        if (!dbus_bus_register(bus, error)) {
+                dbus_connection_close(bus);
+                dbus_connection_unref(bus);
+                return r;
+        }
+
         *_bus = bus;
         return 0;
 }
index 9a66b78..76333cd 100644 (file)
@@ -28,6 +28,9 @@ int bus_check_peercred(DBusConnection *c);
 
 int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private_bus, DBusError *error);
 
+int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error);
+int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error);
+
 const char *bus_error_message(const DBusError *error);
 
 #endif
index bb07b82..a9958c2 100644 (file)
                 <annotate key="org.freedesktop.policykit.exec.path">/lib/systemd/systemd-reply-password</annotate>
         </action>
 
+        <action id="org.freedesktop.systemd1.BusAccess">
+                <description>Privileged system and service manager access</description>
+                <message>Authentication is required to access the system and service manager.</message>
+                <defaults>
+                        <allow_any>no</allow_any>
+                        <allow_inactive>no</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+                <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/systemd-stdio-bridge</annotate>
+        </action>
+
 </policyconfig>
index 5b205fe..b8af654 100644 (file)
@@ -108,6 +108,12 @@ static enum dot {
         DOT_ORDER,
         DOT_REQUIRE
 } arg_dot = DOT_ALL;
+static enum transport {
+        TRANSPORT_NORMAL,
+        TRANSPORT_SSH,
+        TRANSPORT_POLKIT
+} arg_transport = TRANSPORT_NORMAL;
+static const char *arg_host = NULL;
 
 static bool private_bus = false;
 
@@ -2061,12 +2067,14 @@ static void print_status_info(UnitStatusInfo *i) {
 
                 printf("\t  CGroup: %s\n", i->default_control_group);
 
-                if ((c = columns()) > 18)
-                        c -= 18;
-                else
-                        c = 0;
+                if (arg_transport != TRANSPORT_SSH) {
+                        if ((c = columns()) > 18)
+                                c -= 18;
+                        else
+                                c = 0;
 
-                show_cgroup_by_path(i->default_control_group, "\t\t  ", c);
+                        show_cgroup_by_path(i->default_control_group, "\t\t  ", c);
+                }
         }
 
         if (i->need_daemon_reload)
@@ -4290,22 +4298,25 @@ static int systemctl_help(void) {
                "                      pending\n"
                "     --ignore-dependencies\n"
                "                      When queueing a new job, ignore all its dependencies\n"
+               "     --kill-mode=MODE How to send signal\n"
+               "     --kill-who=WHO   Who to send signal to\n"
+               "  -s --signal=SIGNAL  Which signal to send\n"
+               "  -H --host=[user@]host\n"
+               "                      Show information for remote host\n"
+               "  -P --privileged     Acquire privileges before execution\n"
                "  -q --quiet          Suppress output\n"
                "     --no-block       Do not wait until operation finished\n"
-               "     --no-pager       Do not pipe output into a pager.\n"
-               "     --system         Connect to system manager\n"
-               "     --user           Connect to user service manager\n"
-               "     --order          When generating graph for dot, show only order\n"
-               "     --require        When generating graph for dot, show only requirement\n"
                "     --no-wall        Don't send wall message before halt/power-off/reboot\n"
-               "     --global         Enable/disable unit files globally\n"
                "     --no-reload      When enabling/disabling unit files, don't reload daemon\n"
                "                      configuration\n"
+               "     --no-pager       Do not pipe output into a pager.\n"
                "     --no-ask-password\n"
                "                      Do not ask for system passwords\n"
-               "     --kill-mode=MODE How to send signal\n"
-               "     --kill-who=WHO   Who to send signal to\n"
-               "  -s --signal=SIGNAL  Which signal to send\n"
+               "     --order          When generating graph for dot, show only order\n"
+               "     --require        When generating graph for dot, show only requirement\n"
+               "     --system         Connect to system manager\n"
+               "     --user           Connect to user service manager\n"
+               "     --global         Enable/disable unit files globally\n"
                "  -f --force          When enabling unit files, override existing symlinks\n"
                "                      When shutting down, execute action immediately\n"
                "     --defaults       When disabling unit files, remove default symlinks only\n\n"
@@ -4472,6 +4483,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 { "kill-who",  required_argument, NULL, ARG_KILL_WHO  },
                 { "signal",    required_argument, NULL, 's'           },
                 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
+                { "host",      required_argument, NULL, 'H'           },
+                { "privileged",no_argument,       NULL, 'P'           },
                 { NULL,        0,                 NULL, 0             }
         };
 
@@ -4483,7 +4496,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
         /* Only when running as systemctl we ask for passwords */
         arg_ask_password = true;
 
-        while ((c = getopt_long(argc, argv, "ht:p:aqfs:", options, NULL)) >= 0) {
+        while ((c = getopt_long(argc, argv, "ht:p:aqfs:H:P", options, NULL)) >= 0) {
 
                 switch (c) {
 
@@ -4605,6 +4618,15 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                         arg_ask_password = false;
                         break;
 
+                case 'P':
+                        arg_transport = TRANSPORT_POLKIT;
+                        break;
+
+                case 'H':
+                        arg_transport = TRANSPORT_SSH;
+                        arg_host = optarg;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -4614,6 +4636,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 }
         }
 
+        if (arg_transport != TRANSPORT_NORMAL && arg_user) {
+                log_error("Cannot access user instance remotely.");
+                return -EINVAL;
+        }
+
         return 1;
 }
 
@@ -5622,7 +5649,16 @@ int main(int argc, char*argv[]) {
                 goto finish;
         }
 
-        bus_connect(arg_user ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, &private_bus, &error);
+        if (arg_transport == TRANSPORT_NORMAL)
+                bus_connect(arg_user ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, &private_bus, &error);
+        else if (arg_transport == TRANSPORT_POLKIT) {
+                bus_connect_system_polkit(&bus, &error);
+                private_bus = false;
+        } else if (arg_transport == TRANSPORT_SSH) {
+                bus_connect_system_ssh(NULL, arg_host, &bus, &error);
+                private_bus = false;
+        } else
+                assert_not_reached("Uh, invalid transport...");
 
         switch (arg_action) {