mirror of
https://github.com/troglobit/finit.git
synced 2026-11-06 15:51:03 +00: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:
1 parent
ae9a24f44f
commit
0a269f3298
39 files changed
+5530
-49
No files matched your search
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
|
||||
Reference in new issue
Block a user