On the local bus SO_PEERCRED says who is calling and the kernel is the one saying it. Behind a broker one connection carries every caller, so that credential describes dbus-daemon and nothing else, and every privileged method was refused there, root included. Ask the bus driver instead. libink parks the call and hands us the sender; we ask GetConnectionUnixUser and answer when the reply lands, through the same event loop as everything else. Nothing blocks: blocking in PID 1 is why libuEv exists. That needs calls libink can make on a connection it already has, so it gained those too. Answers are cached, since a bus never reuses a unique name while it runs. Not across a restart though: a new dbus-daemon numbers from scratch and :1.7 becomes somebody else, so the cache goes when the broker does. A sender name too long to key on is refused rather than truncated, two callers sharing a truncated key would share an identity. Privilege is no longer uid 0 alone. The socket is already owned by the --with-group group, so refusing its members every method that changes anything left a wheel user able to open the bus and unable to reboot. Both gates now say the same thing. Group membership needs NSS, which the C library loads with dlopen(), so the lookup is compiled out where Finit is built to link statically. That leaves such a build root-only, which is worth saying out loud rather than leaving to be discovered. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
libink — brokerless D-Bus for Finit
libink is a small C library implementing the D-Bus wire protocol,
both the server and the client side, without a broker and without any
dependency on libdbus, sd-bus, or GIO. It was born inside Finit to
let PID 1 be a bus of its own: clients connect straight to the listening
socket, authenticate with the standard SASL EXTERNAL handshake, and get
kernel-authenticated credentials via SO_PEERCRED.
For what the bus exposes and how to talk to it, see the User Guide, D-Bus Integration. This file covers the library itself.
Status
libink is an internal implementation detail of Finit: built as a libtool
convenience library, linked statically into finit and initctl,
nothing installed. There is deliberately no ABI promise yet — that
comes if/when libink is extracted into a project of its own. External
D-Bus clients need none of this; the wire protocol is the compatibility
surface, any standard D-Bus library works.
Layout
| File | Contents |
|---|---|
server.c |
Listening socket, accept, peer credential capture |
auth.c |
SASL EXTERNAL handshake, uid verification |
connection.c |
Per-peer state machine, message framing |
proto.c |
Wire header parse/build |
marshal.c |
Body (de)marshalling: basic types, arrays, variants |
dispatch.c |
Object tree, vtable registration, method dispatch |
builtin.c |
org.freedesktop.DBus.* stock interfaces |
match.c |
AddMatch/RemoveMatch rule parsing and signal filter |
path.c |
systemd-style _HH object path encoding |
client.c |
Outgoing connections, method calls, reply/signal wait |
io.c |
Shared EINTR-resilient read/write loops |
Public API symbols carry the link_* prefix (link.h), internal ones
__* (internal.h). Method handlers are registered as vtables of
link_method_t/link_property_t; the framework emits variant
signatures from the property table so the declared type is the single
source of truth.
The boundary to Finit is deliberate: nothing under libink/ includes a
Finit header. All glue lives in src/dbus.c — object registration,
signal emission from the service/condition/runlevel hook points, and
the uev event loop bridge. initctl uses the client half of the same
library, so one wire-format implementation serves both ends. If libink
is ever spun out, that file is the cut line.
Testing
The test/dbus-*.sh suite exercises the library end to end against a
live Finit in a namespace, driven by test/src/dbus-auth-client.c.
Wire-format conformance against third-party tools (dbus-send,
dbus-monitor) and fuzzing of the parsers are tracked as pre-merge
work — this is PID 1's attack surface.