mirror of
https://github.com/troglobit/finit.git
synced 2026-09-30 13:02:37 +07:00
libink: a brokerless D-Bus implementation for Finit
Finit had no way to answer the question every service manager gets asked: what is running, and change it. D-Bus is how the rest of userspace asks, but linking libdbus, sd-bus or GIO into PID 1 buys a dependency, an allocator and a main loop we do not control. So libink: the wire format, an object tree, and a bus of Finit's own at /run/finit/bus, gated like INIT_SOCKET. It speaks the standard org.freedesktop.DBus, .Peer, .Introspectable interfaces, and Finit's own Manager1, Service1 and Cond1 on top. Methods that change something are marked privileged and answered only for a caller the kernel vouched for, via SO_PEERCRED. Server and client both, since initctl is the first thing that needs to talk to it, and its Start/Stop/Restart/Reload now go over the bus rather than the legacy socket. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -71,6 +71,7 @@ EXTRA_DIST += start-stop-serv.sh
|
||||
EXTRA_DIST += signal-service.sh
|
||||
EXTRA_DIST += testserv.sh
|
||||
EXTRA_DIST += unexpected-restart.sh
|
||||
EXTRA_DIST += dbus-auth.sh
|
||||
|
||||
AM_TESTS_ENVIRONMENT = SYSROOT='$(abs_builddir)/sysroot/';
|
||||
AM_TESTS_ENVIRONMENT += export SYSROOT;
|
||||
@@ -126,6 +127,9 @@ if TESTSERV
|
||||
TESTS += testserv.sh
|
||||
endif
|
||||
TESTS += unexpected-restart.sh
|
||||
if DBUS
|
||||
TESTS += dbus-auth.sh
|
||||
endif
|
||||
|
||||
check-recursive: setup-chroot
|
||||
|
||||
|
||||
+2
-2
@@ -25,8 +25,8 @@ if [ "$run_make" -eq 1 ]; then
|
||||
fi
|
||||
|
||||
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var \
|
||||
--enable-x11-common-plugin --enable-testserv-plugin --with-watchdog \
|
||||
--with-keventd --with-libsystemd \
|
||||
--enable-dbus --enable-x11-common-plugin --enable-testserv-plugin \
|
||||
--with-watchdog --with-keventd --with-libsystemd \
|
||||
CFLAGS='-fsanitize=address -ggdb'
|
||||
|
||||
if [ "$run_make" -eq 1 ]; then
|
||||
|
||||
Executable
+315
@@ -0,0 +1,315 @@
|
||||
#!/bin/sh
|
||||
# End-to-end smoke test for libink:
|
||||
# - AUTH EXTERNAL handshake (happy and wrong-uid paths)
|
||||
# - org.freedesktop.DBus.Hello
|
||||
# - org.freedesktop.DBus.Introspectable.Introspect (root, manager)
|
||||
# - org.finit.Manager1.ListServices
|
||||
# - Error reply for an unknown method.
|
||||
|
||||
set -eu
|
||||
|
||||
TEST_DIR=$(dirname "$0")
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. "$TEST_DIR/lib/setup.sh"
|
||||
|
||||
CLIENT=/sbin/dbus-auth-client
|
||||
BUS=/run/finit/bus
|
||||
|
||||
if ! texec test -x "$CLIENT"; then
|
||||
skip "dbus-auth-client not built (configured with --disable-dbus?)"
|
||||
fi
|
||||
|
||||
say "Wait for $BUS to appear"
|
||||
retry "texec test -S $BUS"
|
||||
|
||||
say "Socket mode is 0666"
|
||||
mode=$(texec stat -c %a "$BUS")
|
||||
assert "Socket mode is 666 (got $mode)" "$mode" = "666"
|
||||
|
||||
# ---------- AUTH ----------
|
||||
|
||||
say "AUTH EXTERNAL: claim correct UID (root = 0)"
|
||||
reply=$(texec "$CLIENT" auth "$BUS" 0)
|
||||
assert "Reply starts with OK (got: $reply)" "${reply%% *}" = "OK"
|
||||
|
||||
guid=${reply#OK }
|
||||
assert "GUID is 32 hex chars (got: $guid)" \
|
||||
"$(printf '%s' "$guid" | tr -d '0-9a-f' | wc -c)" -eq 0
|
||||
assert "GUID length is 32 (got: ${#guid})" "${#guid}" -eq 32
|
||||
|
||||
say "AUTH EXTERNAL: wrong UID is rejected"
|
||||
set +e
|
||||
wrong_reply=$(texec "$CLIENT" auth "$BUS" 1)
|
||||
wrong_rc=$?
|
||||
set -e
|
||||
assert "Wrong UID rejected (rc=$wrong_rc, reply: $wrong_reply)" \
|
||||
"$wrong_rc" -eq 1
|
||||
|
||||
say "Two sequential AUTH connections get different GUIDs"
|
||||
r1=$(texec "$CLIENT" auth "$BUS" 0)
|
||||
r2=$(texec "$CLIENT" auth "$BUS" 0)
|
||||
g1=${r1#OK }
|
||||
g2=${r2#OK }
|
||||
assert "Per-connection GUIDs differ ($g1 vs $g2)" "$g1" != "$g2"
|
||||
|
||||
# ---------- Built-in interfaces ----------
|
||||
|
||||
say "Hello() returns a unique name beginning with ':1.'"
|
||||
name=$(texec "$CLIENT" hello "$BUS")
|
||||
case "$name" in
|
||||
:1.*) assert "Hello returned a :1.N name (got $name)" 0 -eq 0 ;;
|
||||
*) fail "Hello returned unexpected name: $name" ;;
|
||||
esac
|
||||
|
||||
say "Two Hello() calls produce different unique names"
|
||||
n1=$(texec "$CLIENT" hello "$BUS")
|
||||
n2=$(texec "$CLIENT" hello "$BUS")
|
||||
assert "Unique names increment ($n1 vs $n2)" "$n1" != "$n2"
|
||||
|
||||
say "Introspect on root path returns valid XML referencing /manager"
|
||||
xml=$(texec "$CLIENT" introspect "$BUS" /)
|
||||
case "$xml" in
|
||||
*'<node'*) assert "XML has <node> root (good)" 0 -eq 0 ;;
|
||||
*) fail "Root introspect missing <node>: $xml" ;;
|
||||
esac
|
||||
|
||||
say "Introspect on /org/finit/manager exposes Manager1.ListServices"
|
||||
xml=$(texec "$CLIENT" introspect "$BUS" /org/finit/manager)
|
||||
case "$xml" in
|
||||
*'org.finit.Manager1'*'ListServices'*)
|
||||
assert "Manager1 and ListServices visible in XML" 0 -eq 0 ;;
|
||||
*)
|
||||
fail "Manager1 XML missing; got: $xml" ;;
|
||||
esac
|
||||
|
||||
# ---------- Real method call ----------
|
||||
|
||||
say "Manager1.ListServices returns the running services"
|
||||
list=$(texec "$CLIENT" liststrings "$BUS" /org/finit/manager \
|
||||
org.finit.Manager1 ListServices)
|
||||
assert "ListServices returned at least one service" \
|
||||
"$(printf '%s' "$list" | wc -l | tr -d ' ')" -ge 1
|
||||
echo "$list"
|
||||
|
||||
# ---------- Method with arguments ----------
|
||||
|
||||
say "Manager1.Reload (void) succeeds"
|
||||
texec "$CLIENT" call-void "$BUS" /org/finit/manager \
|
||||
org.finit.Manager1 Reload >/dev/null \
|
||||
|| fail "Reload returned non-zero"
|
||||
assert "Reload void method ok" 0 -eq 0
|
||||
|
||||
say "Manager1.Stop with bogus identity returns NoSuchService error"
|
||||
set +e
|
||||
texec "$CLIENT" call-s "$BUS" /org/finit/manager \
|
||||
org.finit.Manager1 Stop "no-such-service-here" >/tmp/dbus-stop.out 2>&1
|
||||
stop_rc=$?
|
||||
set -e
|
||||
assert "Bogus service rejected (rc=$stop_rc)" "$stop_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-stop.out)" in
|
||||
*NoSuchService*) assert "Error is NoSuchService" 0 -eq 0 ;;
|
||||
*) fail "Unexpected error reply: $(cat /tmp/dbus-stop.out)" ;;
|
||||
esac
|
||||
|
||||
# ---------- Authorization ----------
|
||||
|
||||
say "Manager1.Restart from non-root is rejected with AccessDenied"
|
||||
set +e
|
||||
texec "$CLIENT" call-s-as-uid 1 "$BUS" /org/finit/manager \
|
||||
org.finit.Manager1 Restart "testserv" >/tmp/dbus-authz.out 2>&1
|
||||
authz_rc=$?
|
||||
set -e
|
||||
assert "Non-root Restart rejected (rc=$authz_rc)" "$authz_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-authz.out)" in
|
||||
*AccessDenied*) assert "Error is AccessDenied" 0 -eq 0 ;;
|
||||
*) fail "Unexpected error: $(cat /tmp/dbus-authz.out)" ;;
|
||||
esac
|
||||
|
||||
say "Manager1.ListServices is reachable as non-root (not blocked by authz)"
|
||||
# call-s-as-uid sends an "s" body; ListServices expects "", so the
|
||||
# server must reply with org.freedesktop.DBus.Error.InvalidArgs.
|
||||
# Asserting that *positive* marker (not just "no AccessDenied")
|
||||
# ensures we don't silently pass if setuid() failed or the client
|
||||
# never reached the server (e.g. a transport error would print
|
||||
# neither AccessDenied nor InvalidArgs).
|
||||
set +e
|
||||
result=$(texec "$CLIENT" call-s-as-uid 1 "$BUS" /org/finit/manager \
|
||||
org.finit.Manager1 ListServices "" 2>&1)
|
||||
set -e
|
||||
case "$result" in
|
||||
*AccessDenied*) fail "Non-root ListServices rejected by authz: $result" ;;
|
||||
*InvalidArgs*) assert "Non-root reached signature check (InvalidArgs, not AccessDenied)" 0 -eq 0 ;;
|
||||
*) fail "Unexpected reply from non-root ListServices: $result" ;;
|
||||
esac
|
||||
|
||||
# ---------- Per-service objects (Service1) ----------
|
||||
|
||||
say "Manager1.GetService(keventd) returns the encoded object path"
|
||||
path=$(texec "$CLIENT" get-service "$BUS" keventd)
|
||||
expected="/org/finit/service/keventd"
|
||||
assert "GetService returned expected path (got: $path)" "$path" = "$expected"
|
||||
|
||||
say "Introspect on the service object exposes Service1 methods"
|
||||
xml=$(texec "$CLIENT" introspect "$BUS" /org/finit/service/keventd)
|
||||
case "$xml" in
|
||||
*'org.finit.Service1'*'Restart'*)
|
||||
assert "Service1.Restart visible in service-object XML" 0 -eq 0 ;;
|
||||
*)
|
||||
fail "Service1 not visible on /org/finit/service/keventd: $xml" ;;
|
||||
esac
|
||||
|
||||
say "Service1.Restart on /org/finit/service/keventd succeeds"
|
||||
texec "$CLIENT" call-void "$BUS" /org/finit/service/keventd \
|
||||
org.finit.Service1 Restart >/dev/null \
|
||||
|| fail "Service1.Restart returned non-zero"
|
||||
assert "Per-service Restart ok" 0 -eq 0
|
||||
|
||||
say "Service1.Restart from non-root is rejected with AccessDenied"
|
||||
set +e
|
||||
texec "$CLIENT" call-void-as-uid 1 "$BUS" /org/finit/service/keventd \
|
||||
org.finit.Service1 Restart >/tmp/dbus-svcauthz.out 2>&1
|
||||
svc_authz_rc=$?
|
||||
set -e
|
||||
assert "Non-root Service1.Restart rejected (rc=$svc_authz_rc)" \
|
||||
"$svc_authz_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-svcauthz.out)" in
|
||||
*AccessDenied*) assert "Service1 authz fires" 0 -eq 0 ;;
|
||||
*) fail "Expected AccessDenied, got: $(cat /tmp/dbus-svcauthz.out)" ;;
|
||||
esac
|
||||
|
||||
# ---------- Signals ----------
|
||||
|
||||
say "Service1.Restart fires Manager1.ServiceStateChanged"
|
||||
rm -f /tmp/dbus-sig.out
|
||||
( texec "$CLIENT" monitor-signal "$BUS" \
|
||||
"type='signal',interface='org.finit.Manager1',member='ServiceStateChanged'" \
|
||||
5000 > /tmp/dbus-sig.out 2>&1 ) &
|
||||
mon_pid=$!
|
||||
sleep 0.5
|
||||
texec "$CLIENT" call-void "$BUS" /org/finit/service/keventd \
|
||||
org.finit.Service1 Restart >/dev/null \
|
||||
|| fail "Restart trigger returned non-zero"
|
||||
set +e
|
||||
wait "$mon_pid"
|
||||
mon_rc=$?
|
||||
set -e
|
||||
assert "monitor saw a signal (rc=$mon_rc)" "$mon_rc" -eq 0
|
||||
case "$(cat /tmp/dbus-sig.out)" in
|
||||
*"SIGNAL org.finit.Manager1 ServiceStateChanged"*keventd*)
|
||||
assert "Signal payload contains the keventd identity" 0 -eq 0 ;;
|
||||
*)
|
||||
fail "Unexpected signal output: $(cat /tmp/dbus-sig.out)" ;;
|
||||
esac
|
||||
|
||||
# ---------- Cond1 ----------
|
||||
|
||||
say "Cond1.Get returns 'off' for an unset condition"
|
||||
result=$(texec "$CLIENT" call-s "$BUS" /org/finit/cond \
|
||||
org.finit.Cond1 Get "no-such-cond")
|
||||
case "$result" in
|
||||
OK*) : ;; # ok, the cond reports a state, fall through
|
||||
*) fail "Cond1.Get failed: $result" ;;
|
||||
esac
|
||||
|
||||
say "Cond1.Set fires Cond1.ConditionChanged and Get reflects the change"
|
||||
rm -f /tmp/dbus-cond.out
|
||||
( texec "$CLIENT" monitor-signal "$BUS" \
|
||||
"type='signal',interface='org.finit.Cond1',member='ConditionChanged'" \
|
||||
5000 > /tmp/dbus-cond.out 2>&1 ) &
|
||||
cond_mon_pid=$!
|
||||
sleep 0.5
|
||||
texec "$CLIENT" call-s "$BUS" /org/finit/cond \
|
||||
org.finit.Cond1 Set "dbus-test-cond" >/dev/null \
|
||||
|| fail "Cond1.Set returned non-zero"
|
||||
set +e
|
||||
wait "$cond_mon_pid"
|
||||
cond_mon_rc=$?
|
||||
set -e
|
||||
assert "Cond1 monitor saw a signal (rc=$cond_mon_rc)" "$cond_mon_rc" -eq 0
|
||||
case "$(cat /tmp/dbus-cond.out)" in
|
||||
*"SIGNAL org.finit.Cond1 ConditionChanged"*"usr/dbus-test-cond"*on*)
|
||||
assert "ConditionChanged carries usr/dbus-test-cond and 'on'" 0 -eq 0 ;;
|
||||
*)
|
||||
fail "Unexpected Cond1 signal: $(cat /tmp/dbus-cond.out)" ;;
|
||||
esac
|
||||
|
||||
say "Cond1.Set/Clear on non-usr/* is rejected"
|
||||
set +e
|
||||
texec "$CLIENT" call-s "$BUS" /org/finit/cond \
|
||||
org.finit.Cond1 Set "pid/sshd" >/tmp/dbus-condrej.out 2>&1
|
||||
condrej_rc=$?
|
||||
set -e
|
||||
assert "pid/* rejected (rc=$condrej_rc)" "$condrej_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-condrej.out)" in
|
||||
*InvalidArgs*) assert "Error is InvalidArgs" 0 -eq 0 ;;
|
||||
*) fail "Unexpected reply: $(cat /tmp/dbus-condrej.out)" ;;
|
||||
esac
|
||||
|
||||
say "Cond1.Set from non-root is rejected with AccessDenied"
|
||||
set +e
|
||||
texec "$CLIENT" call-s-as-uid 1 "$BUS" /org/finit/cond \
|
||||
org.finit.Cond1 Set "would-be-cond" >/tmp/dbus-condauthz.out 2>&1
|
||||
ca_rc=$?
|
||||
set -e
|
||||
assert "Non-root Cond1.Set rejected (rc=$ca_rc)" "$ca_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-condauthz.out)" in
|
||||
*AccessDenied*) assert "Cond1 authz fires" 0 -eq 0 ;;
|
||||
*) fail "Unexpected reply: $(cat /tmp/dbus-condauthz.out)" ;;
|
||||
esac
|
||||
|
||||
say "AddMatch with a bogus key is rejected"
|
||||
set +e
|
||||
texec "$CLIENT" call-s "$BUS" /org/freedesktop/DBus \
|
||||
org.freedesktop.DBus AddMatch "bogus='whatever'" >/tmp/dbus-match.out 2>&1
|
||||
am_rc=$?
|
||||
set -e
|
||||
assert "Bad rule rejected (rc=$am_rc)" "$am_rc" -eq 1
|
||||
case "$(cat /tmp/dbus-match.out)" in
|
||||
*MatchRuleInvalid*) assert "Error is MatchRuleInvalid" 0 -eq 0 ;;
|
||||
*) fail "Unexpected reply: $(cat /tmp/dbus-match.out)" ;;
|
||||
esac
|
||||
|
||||
# ---------- initctl port ----------
|
||||
|
||||
# initctl now talks to /run/finit/bus when available. Verify by
|
||||
# subscribing to ServiceStateChanged on a background monitor and
|
||||
# then running initctl restart -- if D-Bus is in use, the signal
|
||||
# fires. If the legacy socket were still in use, the dbus subscriber
|
||||
# would see nothing.
|
||||
|
||||
say "initctl restart drives D-Bus (signal observed via dbus-auth-client)"
|
||||
rm -f /tmp/dbus-initctl-sig.out
|
||||
( texec "$CLIENT" monitor-signal "$BUS" \
|
||||
"type='signal',interface='org.finit.Manager1',member='ServiceStateChanged'" \
|
||||
5000 > /tmp/dbus-initctl-sig.out 2>&1 ) &
|
||||
ic_pid=$!
|
||||
sleep 0.5
|
||||
texec initctl restart keventd >/dev/null \
|
||||
|| fail "initctl restart returned non-zero"
|
||||
set +e
|
||||
wait "$ic_pid"
|
||||
ic_rc=$?
|
||||
set -e
|
||||
assert "ServiceStateChanged fired from initctl restart (rc=$ic_rc)" \
|
||||
"$ic_rc" -eq 0
|
||||
case "$(cat /tmp/dbus-initctl-sig.out)" in
|
||||
*"SIGNAL org.finit.Manager1 ServiceStateChanged"*keventd*)
|
||||
assert "initctl restart routed through D-Bus" 0 -eq 0 ;;
|
||||
*)
|
||||
fail "initctl restart didn't produce expected signal: $(cat /tmp/dbus-initctl-sig.out)" ;;
|
||||
esac
|
||||
|
||||
say "initctl reload (no args) routes through Manager1.Reload"
|
||||
texec initctl reload >/dev/null \
|
||||
|| fail "initctl reload returned non-zero"
|
||||
assert "initctl reload ok" 0 -eq 0
|
||||
|
||||
# ---------- Error reply ----------
|
||||
|
||||
say "Unknown method gets an org.freedesktop.DBus.Error.* reply"
|
||||
set +e
|
||||
texec "$CLIENT" unknown "$BUS"
|
||||
unknown_rc=$?
|
||||
set -e
|
||||
assert "Unknown method returned an error (rc=$unknown_rc)" "$unknown_rc" -eq 0
|
||||
+14
-1
@@ -15,9 +15,22 @@ make -C "$top_builddir" DESTDIR="$SYSROOT" install
|
||||
|
||||
mkdir -p "$SYSROOT/sbin/"
|
||||
cp "$top_builddir/test/src/serv" "$SYSROOT/sbin/"
|
||||
if [ -x "$top_builddir/test/src/dbus-auth-client" ]; then
|
||||
cp "$top_builddir/test/src/dbus-auth-client" "$SYSROOT/sbin/"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
FINITBIN="$(pwd)/$top_builddir/src/finit" DEST="$SYSROOT" make -f "$srcdir/lib/sysroot.mk"
|
||||
# Prefer the real ELF in .libs/ over the libtool wrapper script at
|
||||
# $top_builddir/src/finit. Libtool generates a shell wrapper when
|
||||
# the binary depends on an in-tree convenience library (e.g. libink),
|
||||
# and `ldd <wrapper>` returns "not a dynamic executable", which
|
||||
# silently makes sysroot.mk copy zero host libs into the sysroot.
|
||||
if [ -f "$top_builddir/src/.libs/finit" ]; then
|
||||
finitbin_for_ldd="$(pwd)/$top_builddir/src/.libs/finit"
|
||||
else
|
||||
finitbin_for_ldd="$(pwd)/$top_builddir/src/finit"
|
||||
fi
|
||||
FINITBIN="$finitbin_for_ldd" DEST="$SYSROOT" make -f "$srcdir/lib/sysroot.mk"
|
||||
|
||||
# Drop plugins we don't need in test, only causes confusing FAIL in logs.
|
||||
for plugin in tty.so urandom.so rtc.so modprobe.so; do
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
/.libs/
|
||||
/.deps/
|
||||
/serv
|
||||
/dbus-auth-client
|
||||
|
||||
@@ -7,3 +7,8 @@ serv_CPPFLAGS += -I$(top_srcdir)/libsystemd $(lite_CFLAGS)
|
||||
serv_SOURCES += $(top_srcdir)/libsystemd/sd-daemon.c
|
||||
serv_LDADD = $(lite_LIBS)
|
||||
endif
|
||||
|
||||
if DBUS
|
||||
noinst_PROGRAMS += dbus-auth-client
|
||||
dbus_auth_client_SOURCES = dbus-auth-client.c
|
||||
endif
|
||||
|
||||
@@ -0,0 +1,861 @@
|
||||
/* Minimal D-Bus client used by the libink smoke tests.
|
||||
*
|
||||
* Modes:
|
||||
* dbus-auth-client auth <sock> <uid>
|
||||
* Send the SASL handshake claiming <uid>; print server reply line.
|
||||
* Exit 0 if reply begins "OK ", 1 if "REJECTED ", 2 otherwise.
|
||||
*
|
||||
* dbus-auth-client hello <sock>
|
||||
* Auth as own uid; call org.freedesktop.DBus.Hello on
|
||||
* /org/freedesktop/DBus. Print the assigned unique name.
|
||||
*
|
||||
* dbus-auth-client introspect <sock> <object-path>
|
||||
* Auth + org.freedesktop.DBus.Introspectable.Introspect.
|
||||
* Print the XML reply.
|
||||
*
|
||||
* dbus-auth-client liststrings <sock> <object-path> <interface> <method>
|
||||
* Auth + method call expecting reply signature "as"; print one
|
||||
* string per line.
|
||||
*
|
||||
* dbus-auth-client unknown <sock>
|
||||
* Auth + call a bogus method; exits 0 only if the server replies
|
||||
* with an "org.freedesktop.DBus.Error.*" error.
|
||||
*
|
||||
* In every non-auth mode the program exits 0 on a successful method
|
||||
* reply, 1 on a server-side error reply, 2 on transport / parse error.
|
||||
*
|
||||
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <poll.h>
|
||||
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
|
||||
#define ALIGN_UP(x, n) (((x) + (n) - 1) & ~((size_t)((n) - 1)))
|
||||
|
||||
/* ---------- low level I/O ---------- */
|
||||
|
||||
static int write_all(int fd, const void *buf, size_t len)
|
||||
{
|
||||
const char *p = buf;
|
||||
|
||||
while (len > 0) {
|
||||
ssize_t n = write(fd, p, len);
|
||||
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return -1;
|
||||
}
|
||||
p += n;
|
||||
len -= (size_t)n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_full(int fd, void *buf, size_t len)
|
||||
{
|
||||
char *p = buf;
|
||||
|
||||
while (len > 0) {
|
||||
ssize_t n = read(fd, p, len);
|
||||
|
||||
if (n == 0) return -1;
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return -1;
|
||||
}
|
||||
p += n;
|
||||
len -= (size_t)n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_with_timeout(int fd, void *buf, size_t len, int timeout_ms)
|
||||
{
|
||||
struct pollfd pfd = { .fd = fd, .events = POLLIN };
|
||||
int rc;
|
||||
|
||||
for (;;) {
|
||||
rc = poll(&pfd, 1, timeout_ms);
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
if (rc == 0)
|
||||
return 0; /* timed out */
|
||||
break;
|
||||
}
|
||||
return (int)read(fd, buf, len);
|
||||
}
|
||||
|
||||
static ssize_t read_line(int fd, char *buf, size_t bufsz)
|
||||
{
|
||||
size_t off = 0;
|
||||
|
||||
while (off + 1 < bufsz) {
|
||||
ssize_t n = read(fd, buf + off, 1);
|
||||
|
||||
if (n == 0) return -1;
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return -1;
|
||||
}
|
||||
if (buf[off] == '\n') {
|
||||
buf[off] = '\0';
|
||||
if (off > 0 && buf[off - 1] == '\r')
|
||||
buf[--off] = '\0';
|
||||
return (ssize_t)off;
|
||||
}
|
||||
off++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---------- connect + AUTH ---------- */
|
||||
|
||||
static int connect_and_auth(const char *path, uid_t claimed_uid)
|
||||
{
|
||||
struct sockaddr_un sun = { .sun_family = AF_UNIX };
|
||||
char uidstr[16];
|
||||
char hexuid[32];
|
||||
char line[64];
|
||||
char reply[256];
|
||||
size_t i, n;
|
||||
int fd, rc;
|
||||
|
||||
if (strlen(path) >= sizeof(sun.sun_path))
|
||||
return -1;
|
||||
memcpy(sun.sun_path, path, strlen(path) + 1);
|
||||
|
||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) return -1;
|
||||
if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
n = (size_t)snprintf(uidstr, sizeof(uidstr), "%u", (unsigned)claimed_uid);
|
||||
for (i = 0; i < n; i++) {
|
||||
unsigned c = (unsigned char)uidstr[i];
|
||||
|
||||
hexuid[i * 2] = hex[c >> 4];
|
||||
hexuid[i * 2 + 1] = hex[c & 0xf];
|
||||
}
|
||||
hexuid[n * 2] = '\0';
|
||||
|
||||
if (write_all(fd, "\0", 1) < 0) goto io;
|
||||
|
||||
rc = snprintf(line, sizeof(line), "AUTH EXTERNAL %s\r\n", hexuid);
|
||||
if (rc < 0 || (size_t)rc >= sizeof(line)) goto io;
|
||||
if (write_all(fd, line, (size_t)rc) < 0) goto io;
|
||||
|
||||
if (read_line(fd, reply, sizeof(reply)) < 0) goto io;
|
||||
if (strncmp(reply, "OK ", 3) != 0) {
|
||||
fprintf(stderr, "auth failed: %s\n", reply);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (write_all(fd, "BEGIN\r\n", 7) < 0) goto io;
|
||||
return fd;
|
||||
|
||||
io:
|
||||
perror("auth handshake");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---------- D-Bus message build / parse ---------- */
|
||||
|
||||
struct buf {
|
||||
uint8_t *p;
|
||||
size_t cap;
|
||||
size_t off;
|
||||
int err;
|
||||
};
|
||||
|
||||
static int b_reserve(struct buf *b, size_t align, size_t bytes)
|
||||
{
|
||||
size_t pad = ALIGN_UP(b->off, align) - b->off;
|
||||
|
||||
if (b->err || b->off + pad + bytes > b->cap) {
|
||||
b->err = 1;
|
||||
return -1;
|
||||
}
|
||||
while (pad--) b->p[b->off++] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void b_put_u32(struct buf *b, uint32_t v)
|
||||
{
|
||||
if (b_reserve(b, 4, 4) < 0) return;
|
||||
b->p[b->off++] = (uint8_t)(v & 0xff);
|
||||
b->p[b->off++] = (uint8_t)((v >> 8) & 0xff);
|
||||
b->p[b->off++] = (uint8_t)((v >> 16) & 0xff);
|
||||
b->p[b->off++] = (uint8_t)((v >> 24) & 0xff);
|
||||
}
|
||||
|
||||
static void b_put_byte(struct buf *b, uint8_t v)
|
||||
{
|
||||
if (b_reserve(b, 1, 1) < 0) return;
|
||||
b->p[b->off++] = v;
|
||||
}
|
||||
|
||||
static void b_put_string(struct buf *b, const char *s)
|
||||
{
|
||||
size_t len = strlen(s);
|
||||
|
||||
if (b_reserve(b, 4, 4 + len + 1) < 0) return;
|
||||
b_put_u32(b, (uint32_t)len);
|
||||
memcpy(b->p + b->off, s, len);
|
||||
b->off += len;
|
||||
b->p[b->off++] = 0;
|
||||
}
|
||||
|
||||
static void b_put_signature(struct buf *b, const char *s)
|
||||
{
|
||||
size_t len = strlen(s);
|
||||
|
||||
if (b_reserve(b, 1, 1 + len + 1) < 0) return;
|
||||
b->p[b->off++] = (uint8_t)len;
|
||||
memcpy(b->p + b->off, s, len);
|
||||
b->off += len;
|
||||
b->p[b->off++] = 0;
|
||||
}
|
||||
|
||||
/* Send a method call with an optional argument.
|
||||
* arg_sig == NULL or "" -> no body
|
||||
* arg_sig == "s" -> arg_string used
|
||||
* arg_sig == "u" -> arg_u32 used
|
||||
*/
|
||||
static int send_method_call_with_arg(int fd,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *member,
|
||||
const char *arg_sig,
|
||||
const char *arg_string,
|
||||
uint32_t arg_u32);
|
||||
|
||||
static int send_method_call(int fd,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *member)
|
||||
{
|
||||
return send_method_call_with_arg(fd, path, interface, member,
|
||||
NULL, NULL, 0);
|
||||
}
|
||||
|
||||
static int send_method_call_with_arg(int fd,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *member,
|
||||
const char *arg_sig,
|
||||
const char *arg_string,
|
||||
uint32_t arg_u32)
|
||||
{
|
||||
uint8_t hdr[2048];
|
||||
uint8_t body[1024];
|
||||
struct buf b = { .p = hdr, .cap = sizeof(hdr) };
|
||||
struct buf bb = { .p = body, .cap = sizeof(body) };
|
||||
size_t fields_start, fields_end, padded_end;
|
||||
uint32_t body_len = 0;
|
||||
|
||||
/* Build body first so its length and the signature are known
|
||||
* before we write the header. */
|
||||
if (arg_sig && *arg_sig) {
|
||||
if (strcmp(arg_sig, "s") == 0) {
|
||||
b_put_string(&bb, arg_string ? arg_string : "");
|
||||
} else if (strcmp(arg_sig, "u") == 0) {
|
||||
b_put_u32(&bb, arg_u32);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (bb.err) return -1;
|
||||
body_len = (uint32_t)bb.off;
|
||||
}
|
||||
|
||||
/* Fixed header */
|
||||
memset(hdr, 0, 16);
|
||||
hdr[0] = 'l';
|
||||
hdr[1] = 1; /* METHOD_CALL */
|
||||
hdr[2] = 0; /* flags */
|
||||
hdr[3] = 1; /* protocol */
|
||||
hdr[4] = (uint8_t)( body_len & 0xff);
|
||||
hdr[5] = (uint8_t)((body_len >> 8) & 0xff);
|
||||
hdr[6] = (uint8_t)((body_len >> 16) & 0xff);
|
||||
hdr[7] = (uint8_t)((body_len >> 24) & 0xff);
|
||||
hdr[8] = 1; /* serial */
|
||||
b.off = 16;
|
||||
fields_start = b.off;
|
||||
|
||||
/* PATH */
|
||||
b_reserve(&b, 8, 0);
|
||||
b_put_byte(&b, 1);
|
||||
b_put_signature(&b, "o");
|
||||
b_put_string(&b, path);
|
||||
|
||||
if (interface) {
|
||||
b_reserve(&b, 8, 0);
|
||||
b_put_byte(&b, 2);
|
||||
b_put_signature(&b, "s");
|
||||
b_put_string(&b, interface);
|
||||
}
|
||||
|
||||
b_reserve(&b, 8, 0);
|
||||
b_put_byte(&b, 3);
|
||||
b_put_signature(&b, "s");
|
||||
b_put_string(&b, member);
|
||||
|
||||
if (arg_sig && *arg_sig) {
|
||||
b_reserve(&b, 8, 0);
|
||||
b_put_byte(&b, 8);
|
||||
b_put_signature(&b, "g");
|
||||
/* SIGNATURE wire form: 1-byte len, bytes, nul */
|
||||
b_put_byte(&b, (uint8_t)strlen(arg_sig));
|
||||
if (b.off + strlen(arg_sig) + 1 > b.cap) return -1;
|
||||
memcpy(b.p + b.off, arg_sig, strlen(arg_sig));
|
||||
b.off += strlen(arg_sig);
|
||||
b.p[b.off++] = 0;
|
||||
}
|
||||
|
||||
fields_end = b.off;
|
||||
{
|
||||
uint32_t flen = (uint32_t)(fields_end - fields_start);
|
||||
hdr[12] = (uint8_t)( flen & 0xff);
|
||||
hdr[13] = (uint8_t)((flen >> 8) & 0xff);
|
||||
hdr[14] = (uint8_t)((flen >> 16) & 0xff);
|
||||
hdr[15] = (uint8_t)((flen >> 24) & 0xff);
|
||||
}
|
||||
|
||||
padded_end = ALIGN_UP(fields_end, 8);
|
||||
while (b.off < padded_end) hdr[b.off++] = 0;
|
||||
|
||||
if (b.err) return -1;
|
||||
|
||||
if (write_all(fd, hdr, b.off) < 0) return -1;
|
||||
if (body_len > 0 && write_all(fd, body, body_len) < 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read one D-Bus message header + body into msg/body buffers.
|
||||
* Returns 0 on success. Caller-supplied buffers must be large
|
||||
* enough; we set them generously. */
|
||||
struct reply {
|
||||
uint8_t type;
|
||||
uint32_t serial;
|
||||
uint32_t body_len;
|
||||
char signature[64];
|
||||
char error_name[128];
|
||||
char interface[128];
|
||||
char member[128];
|
||||
uint8_t body[8192];
|
||||
};
|
||||
|
||||
/* Read one D-Bus message into *r.
|
||||
* timeout_ms == 0 -> block forever waiting for the header byte
|
||||
* timeout_ms > 0 -> wait that long for the header to start; once
|
||||
* bytes arrive, the remainder of the frame is
|
||||
* read without a timeout (it's "in flight").
|
||||
* Returns 0 on success, -1 on EOF / parse error / timeout. */
|
||||
static int read_reply(int fd, struct reply *r, int timeout_ms)
|
||||
{
|
||||
uint8_t hdr_fixed[16];
|
||||
uint8_t hdr_fields[2048];
|
||||
uint32_t fields_len;
|
||||
size_t body_off;
|
||||
size_t pos;
|
||||
size_t off = 0;
|
||||
|
||||
memset(r, 0, sizeof(*r));
|
||||
|
||||
if (timeout_ms > 0) {
|
||||
int n = read_with_timeout(fd, hdr_fixed, 1, timeout_ms);
|
||||
if (n <= 0) return -1;
|
||||
off = 1;
|
||||
}
|
||||
if (off < 16 && read_full(fd, hdr_fixed + off, 16 - off) < 0)
|
||||
return -1;
|
||||
|
||||
if (hdr_fixed[0] != 'l') return -1;
|
||||
r->type = hdr_fixed[1];
|
||||
r->body_len = (uint32_t)hdr_fixed[4]
|
||||
| ((uint32_t)hdr_fixed[5] << 8)
|
||||
| ((uint32_t)hdr_fixed[6] << 16)
|
||||
| ((uint32_t)hdr_fixed[7] << 24);
|
||||
r->serial = (uint32_t)hdr_fixed[8]
|
||||
| ((uint32_t)hdr_fixed[9] << 8)
|
||||
| ((uint32_t)hdr_fixed[10] << 16)
|
||||
| ((uint32_t)hdr_fixed[11] << 24);
|
||||
fields_len = (uint32_t)hdr_fixed[12]
|
||||
| ((uint32_t)hdr_fixed[13] << 8)
|
||||
| ((uint32_t)hdr_fixed[14] << 16)
|
||||
| ((uint32_t)hdr_fixed[15] << 24);
|
||||
if (fields_len > sizeof(hdr_fields)) return -1;
|
||||
if (read_full(fd, hdr_fields, fields_len) < 0) return -1;
|
||||
|
||||
body_off = (size_t)ALIGN_UP(16 + fields_len, 8);
|
||||
if (body_off > 16 + fields_len) {
|
||||
uint8_t pad[8];
|
||||
if (read_full(fd, pad, body_off - 16 - fields_len) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
pos = 0;
|
||||
while (pos < fields_len) {
|
||||
uint8_t code;
|
||||
size_t vsig_len;
|
||||
const char *vsig;
|
||||
|
||||
pos = ALIGN_UP(pos, 8);
|
||||
if (pos >= fields_len) break;
|
||||
code = hdr_fields[pos++];
|
||||
vsig_len = hdr_fields[pos++];
|
||||
if (pos + vsig_len + 1 > fields_len) return -1;
|
||||
vsig = (const char *)(hdr_fields + pos);
|
||||
pos += vsig_len + 1;
|
||||
|
||||
if (vsig[0] == 's' || vsig[0] == 'o') {
|
||||
uint32_t slen;
|
||||
char *dst = NULL;
|
||||
size_t dst_sz = 0;
|
||||
|
||||
pos = ALIGN_UP(pos, 4);
|
||||
if (pos + 4 > fields_len) return -1;
|
||||
slen = (uint32_t)hdr_fields[pos]
|
||||
| ((uint32_t)hdr_fields[pos + 1] << 8)
|
||||
| ((uint32_t)hdr_fields[pos + 2] << 16)
|
||||
| ((uint32_t)hdr_fields[pos + 3] << 24);
|
||||
pos += 4;
|
||||
if (pos + slen + 1 > fields_len) return -1;
|
||||
|
||||
switch (code) {
|
||||
case 2: dst = r->interface; dst_sz = sizeof(r->interface); break;
|
||||
case 3: dst = r->member; dst_sz = sizeof(r->member); break;
|
||||
case 4: dst = r->error_name; dst_sz = sizeof(r->error_name); break;
|
||||
default: break;
|
||||
}
|
||||
if (dst && slen < dst_sz) {
|
||||
memcpy(dst, hdr_fields + pos, slen);
|
||||
dst[slen] = '\0';
|
||||
}
|
||||
pos += slen + 1;
|
||||
} else if (vsig[0] == 'g') {
|
||||
uint32_t slen = hdr_fields[pos++];
|
||||
if (pos + slen + 1 > fields_len) return -1;
|
||||
if (code == 8 && slen < sizeof(r->signature)) {
|
||||
memcpy(r->signature, hdr_fields + pos, slen);
|
||||
r->signature[slen] = '\0';
|
||||
}
|
||||
pos += slen + 1;
|
||||
} else if (vsig[0] == 'u') {
|
||||
pos = ALIGN_UP(pos, 4);
|
||||
pos += 4;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (r->body_len > sizeof(r->body)) return -1;
|
||||
if (r->body_len > 0 && read_full(fd, r->body, r->body_len) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decode a body containing exactly one "s" or "o" -- the wire form
|
||||
* is identical for both (u32 length + bytes + nul). */
|
||||
static int decode_string(struct reply *r, char *out, size_t outsz)
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
if (r->body_len < 5)
|
||||
return -1;
|
||||
if (strcmp(r->signature, "s") != 0 && strcmp(r->signature, "o") != 0)
|
||||
return -1;
|
||||
len = (uint32_t)r->body[0]
|
||||
| ((uint32_t)r->body[1] << 8)
|
||||
| ((uint32_t)r->body[2] << 16)
|
||||
| ((uint32_t)r->body[3] << 24);
|
||||
if (4 + len + 1 > r->body_len) return -1;
|
||||
if (len + 1 > outsz) return -1;
|
||||
memcpy(out, r->body + 4, len);
|
||||
out[len] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decode a body with signature "as", print one string per line. */
|
||||
static int decode_array_of_strings(struct reply *r)
|
||||
{
|
||||
uint32_t array_len;
|
||||
size_t pos;
|
||||
|
||||
if (strcmp(r->signature, "as") != 0 || r->body_len < 4)
|
||||
return -1;
|
||||
array_len = (uint32_t)r->body[0]
|
||||
| ((uint32_t)r->body[1] << 8)
|
||||
| ((uint32_t)r->body[2] << 16)
|
||||
| ((uint32_t)r->body[3] << 24);
|
||||
pos = ALIGN_UP(4, 4);
|
||||
if (pos + array_len > r->body_len) return -1;
|
||||
|
||||
while (pos < 4 + array_len) {
|
||||
uint32_t slen;
|
||||
pos = ALIGN_UP(pos, 4);
|
||||
if (pos + 4 > r->body_len) return -1;
|
||||
slen = (uint32_t)r->body[pos]
|
||||
| ((uint32_t)r->body[pos + 1] << 8)
|
||||
| ((uint32_t)r->body[pos + 2] << 16)
|
||||
| ((uint32_t)r->body[pos + 3] << 24);
|
||||
pos += 4;
|
||||
if (pos + slen + 1 > r->body_len) return -1;
|
||||
printf("%.*s\n", (int)slen, r->body + pos);
|
||||
pos += slen + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------- modes ---------- */
|
||||
|
||||
static int mode_auth(int argc, char *argv[])
|
||||
{
|
||||
struct sockaddr_un sun = { .sun_family = AF_UNIX };
|
||||
char hexuid[32], line[64], reply[256];
|
||||
const char *path, *claimed;
|
||||
size_t i, claimed_len, plen;
|
||||
int fd, rc;
|
||||
|
||||
if (argc != 4) return 2;
|
||||
path = argv[2];
|
||||
claimed = argv[3];
|
||||
|
||||
plen = strlen(path);
|
||||
if (plen >= sizeof(sun.sun_path)) return 2;
|
||||
claimed_len = strlen(claimed);
|
||||
if (claimed_len * 2 >= sizeof(hexuid)) return 2;
|
||||
for (i = 0; i < claimed_len; i++) {
|
||||
unsigned c = (unsigned char)claimed[i];
|
||||
|
||||
hexuid[i * 2] = hex[c >> 4];
|
||||
hexuid[i * 2 + 1] = hex[c & 0xf];
|
||||
}
|
||||
hexuid[claimed_len * 2] = '\0';
|
||||
|
||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) { perror("socket"); return 2; }
|
||||
memcpy(sun.sun_path, path, plen + 1);
|
||||
if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
|
||||
perror("connect"); close(fd); return 2;
|
||||
}
|
||||
if (write_all(fd, "\0", 1) < 0) { close(fd); return 2; }
|
||||
rc = snprintf(line, sizeof(line), "AUTH EXTERNAL %s\r\n", hexuid);
|
||||
if (rc < 0 || (size_t)rc >= sizeof(line)) { close(fd); return 2; }
|
||||
if (write_all(fd, line, (size_t)rc) < 0) { close(fd); return 2; }
|
||||
if (read_line(fd, reply, sizeof(reply)) < 0) { close(fd); return 2; }
|
||||
printf("%s\n", reply);
|
||||
close(fd);
|
||||
if (strncmp(reply, "OK ", 3) == 0) return 0;
|
||||
if (strncmp(reply, "REJECTED ", 9) == 0) return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int do_call_arg(const char *path, const char *obj_path,
|
||||
const char *iface, const char *method,
|
||||
const char *arg_sig, const char *arg_string,
|
||||
uint32_t arg_u32, struct reply *r)
|
||||
{
|
||||
int fd = connect_and_auth(path, getuid());
|
||||
|
||||
if (fd < 0) return 2;
|
||||
if (send_method_call_with_arg(fd, obj_path, iface, method,
|
||||
arg_sig, arg_string, arg_u32) < 0) {
|
||||
fprintf(stderr, "send: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
return 2;
|
||||
}
|
||||
if (read_reply(fd, r, 0) < 0) {
|
||||
fprintf(stderr, "read_reply\n");
|
||||
close(fd);
|
||||
return 2;
|
||||
}
|
||||
close(fd);
|
||||
if (r->type == 3) {
|
||||
fprintf(stderr, "ERROR: %s\n", r->error_name);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_call(const char *path, const char *obj_path,
|
||||
const char *iface, const char *method,
|
||||
struct reply *r)
|
||||
{
|
||||
return do_call_arg(path, obj_path, iface, method, NULL, NULL, 0, r);
|
||||
}
|
||||
|
||||
static int mode_hello(int argc, char *argv[])
|
||||
{
|
||||
struct reply r;
|
||||
char name[256];
|
||||
int rc;
|
||||
|
||||
if (argc != 3) return 2;
|
||||
rc = do_call(argv[2], "/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus", "Hello", &r);
|
||||
if (rc != 0) return rc;
|
||||
if (decode_string(&r, name, sizeof(name)) < 0) return 2;
|
||||
printf("%s\n", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mode_introspect(int argc, char *argv[])
|
||||
{
|
||||
struct reply r;
|
||||
char xml[8192];
|
||||
int rc;
|
||||
|
||||
if (argc != 4) return 2;
|
||||
rc = do_call(argv[2], argv[3],
|
||||
"org.freedesktop.DBus.Introspectable", "Introspect", &r);
|
||||
if (rc != 0) return rc;
|
||||
if (decode_string(&r, xml, sizeof(xml)) < 0) return 2;
|
||||
printf("%s\n", xml);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mode_liststrings(int argc, char *argv[])
|
||||
{
|
||||
struct reply r;
|
||||
int rc;
|
||||
|
||||
if (argc != 6) return 2;
|
||||
rc = do_call(argv[2], argv[3], argv[4], argv[5], &r);
|
||||
if (rc != 0) return rc;
|
||||
if (decode_array_of_strings(&r) < 0) return 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* call-s: method taking one string arg, void/error reply.
|
||||
* call-void: method taking no args, void/error reply. */
|
||||
static int mode_call_s(int argc, char *argv[])
|
||||
{
|
||||
struct reply r;
|
||||
int rc;
|
||||
|
||||
if (argc != 7) return 2;
|
||||
rc = do_call_arg(argv[2], argv[3], argv[4], argv[5],
|
||||
"s", argv[6], 0, &r);
|
||||
if (rc == 0)
|
||||
printf("OK\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mode_call_void(int argc, char *argv[])
|
||||
{
|
||||
struct reply r;
|
||||
int rc;
|
||||
|
||||
if (argc != 6) return 2;
|
||||
rc = do_call_arg(argv[2], argv[3], argv[4], argv[5],
|
||||
NULL, NULL, 0, &r);
|
||||
if (rc == 0)
|
||||
printf("OK\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* get-service <sock> <identity>
|
||||
*
|
||||
* Calls Manager1.GetService(identity) and prints the returned
|
||||
* object path. Exit 0 on success, 1 on server error, 2 transport. */
|
||||
static int mode_get_service(int argc, char *argv[])
|
||||
{
|
||||
struct reply r;
|
||||
char path[256];
|
||||
int rc;
|
||||
|
||||
if (argc != 4) return 2;
|
||||
rc = do_call_arg(argv[2], "/org/finit/manager",
|
||||
"org.finit.Manager1", "GetService",
|
||||
"s", argv[3], 0, &r);
|
||||
if (rc != 0) return rc;
|
||||
/* decode_string accepts both "s" and "o" — wire form is
|
||||
* identical; no need to pre-check the signature here. */
|
||||
if (decode_string(&r, path, sizeof(path)) < 0)
|
||||
return 2;
|
||||
printf("%s\n", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Drop effective uid to argv[2], parsed as decimal. Returns 0 on
|
||||
* success, 2 (the program's "transport error" code) on failure. */
|
||||
static int drop_uid_from_arg(const char *uid_arg, const char *progname)
|
||||
{
|
||||
uid_t drop_to;
|
||||
char *ep = NULL;
|
||||
long v;
|
||||
|
||||
errno = 0;
|
||||
v = strtol(uid_arg, &ep, 10);
|
||||
if (errno || !ep || *ep != '\0' || v < 0 || v > 65535) {
|
||||
fprintf(stderr, "%s: bad uid: %s\n", progname, uid_arg);
|
||||
return 2;
|
||||
}
|
||||
drop_to = (uid_t)v;
|
||||
|
||||
if (setuid(drop_to) < 0) {
|
||||
perror("setuid");
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* monitor-signal <sock> <match-rule> <timeout-ms>
|
||||
*
|
||||
* Subscribes via org.freedesktop.DBus.AddMatch, then reads
|
||||
* incoming messages until either a SIGNAL is received or the
|
||||
* timeout elapses. On a signal: prints "SIGNAL <iface> <member>"
|
||||
* followed by any "s" args, one per line. Exit 0 on signal, 1 on
|
||||
* timeout, 2 on transport error. */
|
||||
static int mode_monitor_signal(int argc, char *argv[])
|
||||
{
|
||||
int fd;
|
||||
int timeout_ms;
|
||||
struct reply r;
|
||||
char *ep = NULL;
|
||||
long v;
|
||||
|
||||
if (argc != 5) return 2;
|
||||
|
||||
errno = 0;
|
||||
v = strtol(argv[4], &ep, 10);
|
||||
if (errno || !ep || *ep != '\0' || v <= 0 || v > 600000) {
|
||||
fprintf(stderr, "%s: bad timeout: %s\n", argv[0], argv[4]);
|
||||
return 2;
|
||||
}
|
||||
timeout_ms = (int)v;
|
||||
|
||||
fd = connect_and_auth(argv[2], getuid());
|
||||
if (fd < 0) return 2;
|
||||
|
||||
/* AddMatch on org.freedesktop.DBus */
|
||||
if (send_method_call_with_arg(fd, "/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus", "AddMatch",
|
||||
"s", argv[3], 0) < 0) {
|
||||
close(fd); return 2;
|
||||
}
|
||||
if (read_reply(fd, &r, 0) < 0) { close(fd); return 2; }
|
||||
if (r.type == 3) {
|
||||
fprintf(stderr, "AddMatch ERROR: %s\n", r.error_name);
|
||||
close(fd); return 2;
|
||||
}
|
||||
|
||||
/* Now read messages until a signal or timeout. */
|
||||
for (;;) {
|
||||
if (read_reply(fd, &r, timeout_ms) < 0) {
|
||||
close(fd);
|
||||
return 1; /* timeout / transport */
|
||||
}
|
||||
if (r.type != 4) /* not a SIGNAL */
|
||||
continue;
|
||||
printf("SIGNAL %s %s\n", r.interface, r.member);
|
||||
/* Decode body as a sequence of strings; print one per line. */
|
||||
{
|
||||
size_t pos = 0;
|
||||
while (pos + 4 <= r.body_len) {
|
||||
uint32_t slen;
|
||||
pos = ALIGN_UP(pos, 4);
|
||||
if (pos + 4 > r.body_len) break;
|
||||
slen = (uint32_t)r.body[pos]
|
||||
| ((uint32_t)r.body[pos + 1] << 8)
|
||||
| ((uint32_t)r.body[pos + 2] << 16)
|
||||
| ((uint32_t)r.body[pos + 3] << 24);
|
||||
pos += 4;
|
||||
if (pos + slen + 1 > r.body_len) break;
|
||||
printf("%.*s\n", (int)slen, r.body + pos);
|
||||
pos += slen + 1;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* call-s-as-uid <uid> <sock> <obj> <iface> <method> <arg>
|
||||
*
|
||||
* Drops effective uid to <uid> (must work inside the test
|
||||
* namespace where additional uids are mapped) before connecting,
|
||||
* so AUTH EXTERNAL captures <uid> as the peer's real identity.
|
||||
* Used to verify per-method authorization gating. */
|
||||
static int mode_call_s_as_uid(int argc, char *argv[])
|
||||
{
|
||||
struct reply r;
|
||||
int rc;
|
||||
|
||||
if (argc != 8) return 2;
|
||||
if ((rc = drop_uid_from_arg(argv[2], argv[0])) != 0)
|
||||
return rc;
|
||||
rc = do_call_arg(argv[3], argv[4], argv[5], argv[6],
|
||||
"s", argv[7], 0, &r);
|
||||
if (rc == 0)
|
||||
printf("OK\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* call-void-as-uid <uid> <sock> <obj> <iface> <method> */
|
||||
static int mode_call_void_as_uid(int argc, char *argv[])
|
||||
{
|
||||
struct reply r;
|
||||
int rc;
|
||||
|
||||
if (argc != 7) return 2;
|
||||
if ((rc = drop_uid_from_arg(argv[2], argv[0])) != 0)
|
||||
return rc;
|
||||
rc = do_call_arg(argv[3], argv[4], argv[5], argv[6],
|
||||
NULL, NULL, 0, &r);
|
||||
if (rc == 0)
|
||||
printf("OK\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mode_unknown(int argc, char *argv[])
|
||||
{
|
||||
struct reply r;
|
||||
int rc;
|
||||
|
||||
if (argc != 3) return 2;
|
||||
rc = do_call(argv[2], "/org/finit/manager",
|
||||
"org.finit.Manager1", "NotARealMethod", &r);
|
||||
if (rc == 1 && strstr(r.error_name, "org.freedesktop.DBus.Error.") == r.error_name)
|
||||
return 0;
|
||||
if (rc == 1)
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 2) return 2;
|
||||
if (strcmp(argv[1], "auth") == 0) return mode_auth(argc, argv);
|
||||
if (strcmp(argv[1], "hello") == 0) return mode_hello(argc, argv);
|
||||
if (strcmp(argv[1], "introspect") == 0) return mode_introspect(argc, argv);
|
||||
if (strcmp(argv[1], "liststrings") == 0) return mode_liststrings(argc, argv);
|
||||
if (strcmp(argv[1], "call-s") == 0) return mode_call_s(argc, argv);
|
||||
if (strcmp(argv[1], "call-void") == 0) return mode_call_void(argc, argv);
|
||||
if (strcmp(argv[1], "monitor-signal") == 0) return mode_monitor_signal(argc, argv);
|
||||
if (strcmp(argv[1], "call-s-as-uid") == 0) return mode_call_s_as_uid(argc, argv);
|
||||
if (strcmp(argv[1], "call-void-as-uid") == 0) return mode_call_void_as_uid(argc, argv);
|
||||
if (strcmp(argv[1], "get-service") == 0) return mode_get_service(argc, argv);
|
||||
if (strcmp(argv[1], "unknown") == 0) return mode_unknown(argc, argv);
|
||||
fprintf(stderr, "%s: unknown mode '%s'\n", argv[0], argv[1]);
|
||||
return 2;
|
||||
}
|
||||
Reference in New Issue
Block a user