#!/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 *' root (good)" 0 -eq 0 ;; *) fail "Root introspect missing : $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