mirror of
https://github.com/troglobit/finit.git
synced 2026-10-22 11:36:07 +00:00
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>
316 lines
11 KiB
Bash
Executable File
316 lines
11 KiB
Bash
Executable File
#!/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
|