mirror of
https://github.com/troglobit/finit.git
synced 2026-09-30 13:02:37 +07:00
The park machinery was welded to broker uid resolution; a handler that cannot answer yet, like a device-settle call waiting for the event queue to drain, had no way to defer. link_call_park() holds the request, link_call_resume() re-runs the handler with link_call_resumed() reading true, and the expire sweep remains the backstop for a resume that never comes. Resume also no longer drops a local caller's kernel group set in the privileged re-check: group source now keys on broker-ness. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
234 lines
8.6 KiB
C
234 lines
8.6 KiB
C
/* libink internal types — not for external consumers.
|
|
*
|
|
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
#ifndef LIBINK_INTERNAL_H_
|
|
#define LIBINK_INTERNAL_H_
|
|
|
|
#include <stdint.h>
|
|
#include <sys/queue.h>
|
|
|
|
#include "link.h"
|
|
#include "marshal.h"
|
|
#include "proto.h"
|
|
|
|
typedef enum {
|
|
LINK_AUTH_NUL = 0,
|
|
LINK_AUTH_LINE,
|
|
LINK_AUTH_DONE,
|
|
LINK_AUTH_FAILED,
|
|
} link_auth_state_t;
|
|
|
|
#define LINK_PATH_MAX 108
|
|
#define LINK_AUTH_LINEBUF_SIZE 256
|
|
#define LINK_RX_BUF_SIZE (64 * 1024)
|
|
#define LINK_TX_BUF_SIZE (16 * 1024)
|
|
#define LINK_UNIQUE_NAME_LEN LINK_SENDER_MAX
|
|
#define LINK_MATCH_RULE_MAX 256 /* per-peer match rule cap */
|
|
#define LINK_MATCH_PEER_CAP 16 /* max active match rules per peer */
|
|
#define LINK_PENDING_CAP 4 /* outbound calls awaiting a reply */
|
|
/* Primary gid plus supplementary groups captured from SO_PEERGROUPS at
|
|
* accept, so the authorizer can answer group membership without an NSS
|
|
* lookup in PID 1. A peer in more groups than this keeps only its
|
|
* primary gid (see link_server_accept), which fails closed. 32 covers
|
|
* a normal login; a directory environment can exceed it. */
|
|
#define LINK_PEER_GROUPS_MAX 32
|
|
/* Staging for an outgoing method call. Generous on purpose: headers
|
|
* for the calls libink makes run to ~150 B, and both the synchronous
|
|
* and the connection-side path build into these, so one answer rather
|
|
* than a number per call site. */
|
|
#define LINK_CALL_HDR_MAX 1024
|
|
#define LINK_CALL_BODY_MAX 1024
|
|
#define LINK_PARKED_CAP 4 /* inbound calls held for later */
|
|
/* A call parked for authorization is a privileged one: an object path
|
|
* and at most a service name. Finit's per-service paths alone run to
|
|
* 512 bytes, so leave room for the header around one. Anything that
|
|
* does not fit is denied rather than held. */
|
|
#define LINK_PARKED_MSG_MAX 1024
|
|
|
|
/* Per-vtable record attached to an object's interface list. */
|
|
struct link_vtable_entry {
|
|
const link_vtable_t *vt;
|
|
void *userdata;
|
|
TAILQ_ENTRY(link_vtable_entry) link;
|
|
};
|
|
|
|
TAILQ_HEAD(link_vtable_list, link_vtable_entry);
|
|
|
|
/* An object exposed at one path. */
|
|
struct link_object {
|
|
char path[LINK_PATH_MAX];
|
|
struct link_vtable_list vtables;
|
|
TAILQ_ENTRY(link_object) link;
|
|
};
|
|
|
|
TAILQ_HEAD(link_object_list, link_object);
|
|
|
|
/* An inbound method call held while we find out who sent it. The
|
|
* message is copied because rxbuf is reused as soon as we return to
|
|
* the read loop. `tok` is the handle the resolver answers with, and
|
|
* zero when the slot is free. `stamp` is when it was parked, for
|
|
* link_connection_expire(). */
|
|
struct link_parked {
|
|
link_authz_t tok;
|
|
link_connection_t *conn;
|
|
uint64_t stamp;
|
|
uid_t uid; /* caller, for handler-parked calls */
|
|
size_t len;
|
|
uint8_t buf[LINK_PARKED_MSG_MAX];
|
|
};
|
|
|
|
/* Calls in flight in either direction: inbound ones held while we ask
|
|
* who sent them, outbound ones waiting for their reply. Both belong
|
|
* to a conversation with a broker, so this hangs off the connection
|
|
* and is allocated on first use. An ordinary peer, which only ever
|
|
* calls in and is identified by SO_PEERCRED, never gets one.
|
|
*
|
|
* Tokens are handed out per bus, which is all link_uid_resolved()
|
|
* needs: it is told the connection the answer belongs to. */
|
|
struct link_bus {
|
|
struct link_parked parked[LINK_PARKED_CAP];
|
|
link_authz_t next_tok;
|
|
|
|
/* Outbound calls we made on this connection, awaiting replies. */
|
|
struct {
|
|
int used;
|
|
uint32_t serial;
|
|
uint64_t stamp; /* for link_connection_expire() */
|
|
link_reply_cb_t cb;
|
|
void *userdata;
|
|
} pending[LINK_PENDING_CAP];
|
|
};
|
|
|
|
struct link_server {
|
|
int fd;
|
|
char path[LINK_PATH_MAX];
|
|
struct link_object_list objects;
|
|
uint32_t next_unique_id; /* for ":1.N" names */
|
|
|
|
/* Set by link_server_set_uid_resolver(); see link.h. */
|
|
link_uid_resolver_t uid_resolver;
|
|
void *uid_userdata;
|
|
|
|
/* Set by link_server_set_authorizer(); see link.h. */
|
|
link_authorizer_t authorizer;
|
|
void *authz_userdata;
|
|
};
|
|
|
|
/* The reply being assembled inside a method handler.
|
|
*
|
|
* The reply body lives in conn->txbuf, not on this struct, so a
|
|
* stack-allocated link_call (in dispatch) stays small. Sharing the
|
|
* connection's txbuf is safe because a reply is marshalled and sent
|
|
* without yielding. Note that parking means several calls can be in
|
|
* flight on one connection: what is held is the request, and
|
|
* link_uid_resolved() resumes from a copy, so txbuf is still only
|
|
* ever used by one reply at a time. An async handler that returned
|
|
* before writing its reply would break that. */
|
|
struct link_call {
|
|
link_connection_t *conn;
|
|
struct link_msg incoming;
|
|
struct link_reader read_cursor;
|
|
struct link_writer reply_writer; /* writes into conn->txbuf */
|
|
int reply_consumed;
|
|
int error_sent;
|
|
uid_t uid; /* caller, resolved for a broker peer */
|
|
const uint8_t *frame; /* raw frame, for link_call_park() */
|
|
size_t framelen;
|
|
int parked; /* handler deferred its reply */
|
|
int resumed; /* re-run via link_call_resume() */
|
|
};
|
|
|
|
/* A parsed AddMatch rule. Fields are NULL when the rule omits the
|
|
* key, meaning "match anything"; non-NULL means "must equal". */
|
|
struct link_match {
|
|
char *raw; /* original string, for RemoveMatch */
|
|
char *type; /* "signal", or NULL */
|
|
char *interface;
|
|
char *member;
|
|
char *path;
|
|
};
|
|
|
|
struct link_connection {
|
|
int fd;
|
|
uid_t peer_uid;
|
|
gid_t peer_groups[LINK_PEER_GROUPS_MAX];
|
|
int peer_ngroups; /* 0 until captured at accept */
|
|
|
|
char guid[33];
|
|
char unique_name[LINK_UNIQUE_NAME_LEN]; /* ":1.N" */
|
|
|
|
link_auth_state_t auth;
|
|
char linebuf[LINK_AUTH_LINEBUF_SIZE];
|
|
size_t linelen;
|
|
|
|
/* Match rules registered via org.freedesktop.DBus.AddMatch.
|
|
* Bounded for PID 1 hygiene; a peer that exceeds the cap gets
|
|
* a LimitsExceeded error reply. A broker never registers any,
|
|
* it matches for its own clients, so `broker` bypasses them. */
|
|
struct link_match *matches[LINK_MATCH_PEER_CAP];
|
|
size_t matches_count;
|
|
int broker;
|
|
|
|
uint8_t rxbuf[LINK_RX_BUF_SIZE];
|
|
size_t rxlen;
|
|
|
|
/* Scratch for outgoing reply bodies. Shared by the dispatch
|
|
* path (writes through call.reply_writer) and built-in handlers
|
|
* (send_string_reply). Lifetime ends with each send_method_*
|
|
* call. */
|
|
uint8_t txbuf[LINK_TX_BUF_SIZE];
|
|
|
|
uint32_t next_serial;
|
|
|
|
/* Allocated on the first park or outbound call, see above. */
|
|
struct link_bus *bus;
|
|
|
|
struct link_server *server; /* back-pointer for dispatch */
|
|
};
|
|
|
|
/* log.c — tracing, no-op unless the embedder installed a callback. */
|
|
void __log(const char *func, const char *fmt, ...)
|
|
__attribute__((format(printf, 2, 3)));
|
|
#define __dbg(fmt, ...) __log(__func__, fmt, ##__VA_ARGS__)
|
|
|
|
/* io.c — shared EINTR-resilient I/O loops, and the clock the expiry
|
|
* sweeps measure against. */
|
|
int __io_write_all(int fd, const void *buf, size_t len);
|
|
int __io_read_full(int fd, void *buf, size_t len);
|
|
uint64_t __now_ms(void);
|
|
|
|
/* auth.c */
|
|
int __auth_process(link_connection_t *conn);
|
|
void __auth_generate_guid(char out[33]);
|
|
int __auth_client(int fd, uid_t uid);
|
|
|
|
/* connection.c — the per-connection bus state, made on demand. */
|
|
struct link_bus *__bus_get (link_connection_t *conn);
|
|
void __bus_free(link_connection_t *conn);
|
|
|
|
/* dispatch.c */
|
|
int __dispatch_message(link_connection_t *conn, const struct link_msg *m, size_t framelen);
|
|
void __dispatch_forget_conn(link_connection_t *conn);
|
|
int __dispatch_expire_parked(link_connection_t *conn, unsigned int age_ms);
|
|
int __send_error(link_connection_t *conn, const struct link_msg *req,
|
|
const char *error_name, const char *text);
|
|
int __send_method_return(link_connection_t *conn, const struct link_msg *req,
|
|
const char *out_sig,
|
|
const uint8_t *body, size_t body_len);
|
|
|
|
/* builtin.c */
|
|
int __handle_builtin(link_connection_t *conn, const struct link_msg *m);
|
|
|
|
/* match.c */
|
|
struct link_match *__match_parse (const char *rule);
|
|
void __match_free (struct link_match *m);
|
|
int __match_matches(const struct link_match *m,
|
|
const char *path, const char *iface,
|
|
const char *member);
|
|
int __match_add (link_connection_t *conn, const char *rule);
|
|
int __match_remove (link_connection_t *conn, const char *rule);
|
|
|
|
#endif /* LIBINK_INTERNAL_H_ */
|