diff --git a/Makefile b/Makefile index 149d266..be81add 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ # make -> build/make/{imsd,imsd-media,imsd-dialerd,imsd-ofonod} # make check -> build + run the 7 unit-test suites # make install -> DESTDIR/PREFIX staged install (binaries + systemd -# units, D-Bus policies, dialerd autostart, the GNOME +# units, D-Bus and polkit policies, dialerd autostart, the GNOME # Calls plugin override, the imsd-ofono preset, and the libexec scripts # ims-pdn-up.sh, imsd-handover.sh, imsd-ofono-restart-calls.sh) # @@ -75,6 +75,12 @@ $(PCM)/Imsd-Engine.pcm: $(PCM)/Imsd-Util.pcm $(PCM)/Imsd-Sip.pcm \ $(PCM)/Imsd-Sdp.pcm $(PCM)/Imsd-Messages.pcm $(PCM)/Imsd.pcm: $(PARTITIONS:%=$(PCM)/%.pcm) +# ImsdBus: the polkit gate imsd and imsd-ofonod share — GLib, so outside +# imsd-core, and its BMI needs the gio include paths. +$(PCM)/ImsdBus.pcm: interfaces/ImsdBus.cppm $(PCM)/std.pcm | $(PCM) + $(CXX) $(ALL_CXXFLAGS) $(GIO_CFLAGS) -fprebuilt-module-path=$(PCM) \ + --precompile $< -o $@ + # ---- imsd-core (static lib of the pure-C++ engine modules) ---------------- $(OBJ)/%.o: $(PCM)/%.pcm | $(OBJ) @@ -85,11 +91,11 @@ $(O)/libimsd-core.a: $(CORE_OBJS) # ---- executables ----------------------------------------------------------- -$(OBJ)/main.o: implementations/main.cpp $(PCM)/Imsd.pcm | $(OBJ) +$(OBJ)/main.o: implementations/main.cpp $(PCM)/Imsd.pcm $(PCM)/ImsdBus.pcm | $(OBJ) $(CXX) $(ALL_CXXFLAGS) $(GIO_CFLAGS) -fprebuilt-module-path=$(PCM) \ -c $< -o $@ -$(O)/imsd: $(OBJ)/main.o $(O)/libimsd-core.a +$(O)/imsd: $(OBJ)/main.o $(OBJ)/ImsdBus.o $(O)/libimsd-core.a $(CXX) $(ALL_CXXFLAGS) $(ALL_LDFLAGS) $^ $(GIO_LIBS) -lpthread -o $@ $(OBJ)/media.o: implementations/media.cpp $(PCM)/std.pcm | $(OBJ) @@ -105,11 +111,11 @@ $(OBJ)/dialerd.o: implementations/dialerd.cpp $(PCM)/std.pcm | $(OBJ) $(O)/imsd-dialerd: $(OBJ)/dialerd.o $(CXX) $(ALL_CXXFLAGS) $(ALL_LDFLAGS) $^ $(GIO_LIBS) -o $@ -$(OBJ)/ofonod.o: implementations/ofonod.cpp $(PCM)/std.pcm | $(OBJ) +$(OBJ)/ofonod.o: implementations/ofonod.cpp $(PCM)/ImsdBus.pcm | $(OBJ) $(CXX) $(ALL_CXXFLAGS) $(GIO_CFLAGS) -fprebuilt-module-path=$(PCM) \ -c $< -o $@ -$(O)/imsd-ofonod: $(OBJ)/ofonod.o +$(O)/imsd-ofonod: $(OBJ)/ofonod.o $(OBJ)/ImsdBus.o $(CXX) $(ALL_CXXFLAGS) $(ALL_LDFLAGS) $^ $(GIO_LIBS) -o $@ # ---- tests ----------------------------------------------------------------- @@ -142,6 +148,8 @@ install: all $(DESTDIR)/etc/xdg/autostart/imsd-dialerd.desktop install -Dm644 packaging/net.catcrafts.IMS1.conf \ $(DESTDIR)$(PREFIX)/share/dbus-1/system.d/net.catcrafts.IMS1.conf + install -Dm644 packaging/net.catcrafts.IMS1.policy \ + $(DESTDIR)$(PREFIX)/share/polkit-1/actions/net.catcrafts.IMS1.policy install -Dm644 packaging/imsd-ofonod.service \ $(DESTDIR)$(PREFIX)/lib/systemd/system/imsd-ofonod.service install -Dm755 packaging/imsd-ofono-restart-calls.sh \ diff --git a/README.md b/README.md index 08cfc8a..67797cb 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,12 @@ every decision — SIP framing, SDP selection, the AKAv1-MD5 digest, the `ip xfrm` command sequence, and the whole call FSM — is unit-tested on a dev box with no bus, modem, or phone; several suites pin output byte-for-byte against recorded network traffic. GLib/GDBus, sockets, and subprocess control -live only in the `imsd` daemon shell. +live only in the `imsd` daemon shell, apart from imsd-bus, the GLib polkit +gate it shares with imsd-ofonod. ## Architecture -C++26 modules, built with Crafter Build. Five build products: +C++26 modules, built with Crafter Build. Six build products: - **imsd-core** (static library) — the engine, pure `import std` C++ with no GLib and no I/O side effects. Everything here is unit-testable on any dev @@ -37,7 +38,12 @@ C++26 modules, built with Crafter Build. Five build products: byte-for-byte against recorded network traffic. - **imsd** (executable) — the daemon shell: GLib main loop, GDBus service, sockets, subprocess control (`qmicli`/`mmcli`/`ip`), and the registration - sequence + run loop that pumps the core. All GLib and I/O live here. + sequence + run loop that pumps the core. +- **imsd-bus** (static library) — the polkit gate in front of call control + (`net.catcrafts.IMS1.Voice`), which imsd and imsd-ofonod both link. GLib, + so kept outside imsd-core. A translation daemon that runs as root on the + system bus reaches imsd as root, which polkit always allows, so it gates + its own callers through this or it is a bypass. - **imsd-media** (executable) — the RTP/AMR-WB data plane, spawned once per call. Pure `import std` + POSIX, no GLib; links `-ldl` and dlopen's the codecs. A separate process for crash isolation and the far-end-hangup @@ -52,7 +58,8 @@ C++26 modules, built with Crafter Build. Five build products: - **imsd-ofonod** (executable) — the GNOME Calls integration, for Phosh and GNOME Mobile: a system daemon owning `org.ofono` and presenting imsd as an oFono modem, which is the seam GNOME Calls' bundled `ofono` provider plugin - drives. GDBus translation only, no core; built with `-- --product=ofonod`. + drives. GDBus translation, no core, plus the imsd-bus gate on its own + callers; built with `-- --product=ofonod`. See [GNOME Calls (Phosh, GNOME Mobile)](#gnome-calls-phosh-gnome-mobile). ### Desktop integration @@ -144,10 +151,12 @@ crafter-build -- --target=aarch64-alpine-linux-musl \ busctl --user call net.catcrafts.IMS1 /net/catcrafts/IMS1 net.catcrafts.IMS1 GetStatus ``` -On a phone the daemon runs as root on the system bus (policy in -`packaging/net.catcrafts.IMS1.conf` — it grants the postmarketOS default -account `user` access; adjust it if your session account is named -differently), started by `packaging/imsd.service` +On a phone the daemon runs as root on the system bus, where any local +account may reach it (`packaging/net.catcrafts.IMS1.conf`); placing, +answering and ending a call is polkit's `net.catcrafts.IMS1.Voice` +(`packaging/net.catcrafts.IMS1.policy`, checked by imsd and imsd-ofonod +alike): the active local session — the dialer, whatever the account is +called — and root, not an ssh login. It is started by `packaging/imsd.service` after `packaging/ims-pdn-up.sh` has brought up the ims PDN through ModemManager. An apk upgrade restarts `imsd` and `imsd-ofonod` once the transaction is done (a call in progress ends), as does adding a package @@ -398,8 +407,10 @@ integrations depend on it: | `CallDeleted` | `s` | | `RegistrationChanged` | `b` | -Both `imsd-dialerd` and `imsd-ofonod` are pure translations of this ABI; a new -desktop needs a new translation daemon, not changes here. +Both `imsd-dialerd` and `imsd-ofonod` translate this ABI and add no members +to it; a new desktop needs a new translation daemon, not changes here — one +that runs as root on the system bus also gates its own callers through +imsd-bus (see Architecture), or it bypasses `net.catcrafts.IMS1.Voice`. ## License diff --git a/implementations/main.cpp b/implementations/main.cpp index 2d5e318..81103b8 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -11,8 +11,10 @@ Owns net.catcrafts.IMS1 on the system bus with the project's frozen D-Bus ABI run outgoing and incoming calls through the Imsd:Engine call machine (inbound INVITEs on the protected-port listeners become ringing calls a dialer answers via Accept), and spawn one imsd-media data-plane process per answered call. -GLib/GDBus lives only here; every message, digest, SA command, and call-state -decision comes from the GLib-free core. +GLib/GDBus lives only here and in ImsdBus (the polkit gate in front of call +control, shared with imsd-ofonod) — the integration daemons imsd-dialerd and +imsd-ofonod carry their own; every message, digest, SA command, and +call-state decision comes from the GLib-free core. Threads: a dedicated engine thread runs the SIP loop (blocking framed recv, protected-port listeners, media reaping, keepalive); the GLib main loop owns @@ -20,7 +22,8 @@ the bus. D-Bus method calls push commands to the engine queue; engine events reach the bus via g_idle_add, where the calls table + status snapshot live (touched only on the main thread). - --session own the name on the session bus (development; no policy needed) + --session own the name on the session bus (development; no policy, no + polkit) */ #include @@ -40,6 +43,7 @@ reach the bus via g_idle_add, where the calls table + status snapshot live import std; import Imsd; +import ImsdBus; namespace { @@ -2408,7 +2412,9 @@ void HandleMethodCall(GDBusConnection*, const gchar*, const gchar*, const gchar* g_dbus_method_invocation_return_dbus_error(inv, "org.freedesktop.DBus.Error.UnknownMethod", "no such method"); } -const GDBusInterfaceVTable Vtable = { HandleMethodCall, nullptr, nullptr, {} }; +bool IsRead(std::string_view method) { return method == "GetStatus" || method == "GetCalls"; } +constexpr char ErrAccessDenied[] = "org.freedesktop.DBus.Error.AccessDenied"; +const GDBusInterfaceVTable Vtable = { imsd::bus::Gated, nullptr, nullptr, {} }; void OnBusAcquired(GDBusConnection* conn, const gchar*, gpointer) { DbusConn = conn; @@ -2455,6 +2461,7 @@ int main(int argc, char** argv) { } Log(std::format("LOCAL={} P-CSCF=[{}]:{}", local, EnvOr("PCSCF", "(unset)"), EnvOr("PCSCF_PORT", "5060"))); if (!session) LogAudioSession(); + imsd::bus::AuthorizeCalls = !session; Engine engine; engine.SetLocal(local); diff --git a/implementations/ofonod.cpp b/implementations/ofonod.cpp index a93bb15..2122b56 100644 --- a/implementations/ofonod.cpp +++ b/implementations/ofonod.cpp @@ -45,11 +45,12 @@ plugins/provider/ofono/ — get these wrong and the plugin silently ignores us: Runs as root because `org.ofono` is a system-bus name (policy in packaging/org.ofono.conf). It does no I/O beyond D-Bus: every call decision -stays in imsd. Everything happens on the GLib main loop, so there is no -locking. +stays in imsd. Its calls reach imsd as root, so it asks polkit about its own +callers (ImsdBus): call control is net.catcrafts.IMS1.Voice, as on imsd. +Everything happens on the GLib main loop, so there is no locking. --session own org.ofono on the session bus and talk to imsd there - (development: no root, no policy file) + (development: no root, no policy file, no polkit) IMSD_BUS=session|system override only the imsd side of that choice */ @@ -57,6 +58,7 @@ locking. #include import std; +import ImsdBus; namespace { @@ -315,7 +317,9 @@ void HandleCallMethod(GDBusConnection*, const gchar*, const gchar* objectPath, c g_dbus_method_invocation_return_dbus_error(inv, "org.freedesktop.DBus.Error.UnknownMethod", "no such method"); } -const GDBusInterfaceVTable CallVtable = { HandleCallMethod, nullptr, nullptr, {} }; +bool IsRead(std::string_view method) { return method == "GetProperties" || method == "GetCalls" || method == "GetModems"; } +constexpr char ErrAccessDenied[] = "org.ofono.Error.AccessDenied"; +const GDBusInterfaceVTable CallVtable = { imsd::bus::Gated, nullptr, nullptr, {} }; guint RegisterObject(const char* xml, const char* path, const GDBusInterfaceVTable* vtable) { GDBusNodeInfo* node = g_dbus_node_info_new_for_xml(xml, nullptr); @@ -596,9 +600,9 @@ void HandleSsMethod(GDBusConnection*, const gchar*, const gchar*, const gchar*, } const GDBusInterfaceVTable ManagerVtable = { HandleManagerMethod, nullptr, nullptr, {} }; -const GDBusInterfaceVTable ModemVtable = { HandleModemMethod, nullptr, nullptr, {} }; -const GDBusInterfaceVTable VcmVtable = { HandleVcmMethod, nullptr, nullptr, {} }; -const GDBusInterfaceVTable SsVtable = { HandleSsMethod, nullptr, nullptr, {} }; +const GDBusInterfaceVTable ModemVtable = { imsd::bus::Gated, nullptr, nullptr, {} }; +const GDBusInterfaceVTable VcmVtable = { imsd::bus::Gated, nullptr, nullptr, {} }; +const GDBusInterfaceVTable SsVtable = { imsd::bus::Gated, nullptr, nullptr, {} }; // ---- imsd events ------------------------------------------------------------ void PublishVoiceState() { @@ -727,6 +731,7 @@ int main(int argc, char** argv) { if (std::string_view(argv[i]) == "--session") session = true; GBusType ofonoBus = session ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM; + imsd::bus::AuthorizeCalls = !session; std::string imsdBusName = EnvOr("IMSD_BUS", session ? "session" : "system"); GBusType imsdBus = imsdBusName == "session" ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM; diff --git a/interfaces/ImsdBus.cppm b/interfaces/ImsdBus.cppm new file mode 100644 index 0000000..4156d88 --- /dev/null +++ b/interfaces/ImsdBus.cppm @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +// lint-disable-file fixed-width-types no-char-pointer +/* +ImsdBus — what the two system-bus daemons (imsd, imsd-ofonod) share on the +bus side: the polkit gate in front of call control. + +The bus policies let any local account reach both names, so the session +account can dial whatever it is called; whether a caller may place, answer +or end a call is polkit's net.catcrafts.IMS1.Voice +(packaging/net.catcrafts.IMS1.policy), shaped like ModemManager's Voice +action: the active local session and root, not an ssh login, an inactive +session or a system service account. Reads (status, the call list) stay +open, as ModemManager's are to every local account; the call signals reach +them all anyway. + +GLib, so not part of the GLib-free imsd-core. +*/ + +module; +#include +export module ImsdBus; +import std; + +namespace imsd::bus { + +constexpr const char* VoiceAction = "net.catcrafts.IMS1.Voice"; + +// CheckAuthorization's AllowUserInteraction, as ModemManager passes it: a +// rule an administrator sets to auth_self prompts through the session's +// polkit agent. +constexpr guint32 AllowUserInteraction = 1; + +struct Pending { + GDBusMethodInvocation* inv; + GDBusInterfaceMethodCallFunc body; + const char* deniedError; + void (*log)(std::string_view); +}; + +void OnCheck(GObject* src, GAsyncResult* res, gpointer data) { + std::unique_ptr p(static_cast(data)); + GDBusMethodInvocation* inv = p->inv; + GError* err = nullptr; + GVariant* r = g_dbus_connection_call_finish(G_DBUS_CONNECTION(src), res, &err); + gboolean authorized = FALSE; + gboolean challenge = FALSE; + if (r) { + GVariant* details = nullptr; + g_variant_get(r, "((bb@a{ss}))", &authorized, &challenge, &details); + g_variant_unref(details); + g_variant_unref(r); + } + if (authorized) { + p->body(g_dbus_method_invocation_get_connection(inv), g_dbus_method_invocation_get_sender(inv), + g_dbus_method_invocation_get_object_path(inv), g_dbus_method_invocation_get_interface_name(inv), + g_dbus_method_invocation_get_method_name(inv), g_dbus_method_invocation_get_parameters(inv), inv, + g_dbus_method_invocation_get_user_data(inv)); + return; + } + std::string why = err ? std::format("{}: polkit did not answer: {}", g_dbus_method_invocation_get_sender(inv), err->message) + : std::format("{} is not authorized for {}", g_dbus_method_invocation_get_sender(inv), VoiceAction); + p->log(std::format("{} refused: {}", g_dbus_method_invocation_get_method_name(inv), why)); + g_dbus_method_invocation_return_dbus_error(inv, p->deniedError, why.c_str()); + if (err) g_error_free(err); +} + +} // namespace imsd::bus + +export namespace imsd::bus { + +// Off for a --session run: the session bus has no polkit, and its callers +// are the owner's own processes. +inline bool AuthorizeCalls = true; + +// Run `body` for the call `inv` once polkit allows its sender +// net.catcrafts.IMS1.Voice; otherwise answer `deniedError` and log why. The +// check is asynchronous — nothing else on the main loop waits for it. +void Authorize(GDBusMethodInvocation* inv, GDBusInterfaceMethodCallFunc body, const char* deniedError, void (*log)(std::string_view)) { + GVariantBuilder subject; + g_variant_builder_init(&subject, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&subject, "{sv}", "name", g_variant_new_string(g_dbus_method_invocation_get_sender(inv))); + GVariantBuilder details; + g_variant_builder_init(&details, G_VARIANT_TYPE("a{ss}")); + g_dbus_connection_call(g_dbus_method_invocation_get_connection(inv), "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization", + g_variant_new("((sa{sv})sa{ss}us)", "system-bus-name", &subject, VoiceAction, &details, AllowUserInteraction, ""), + G_VARIANT_TYPE("((bba{ss}))"), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, nullptr, OnCheck, + new Pending{ inv, body, deniedError, log }); +} + +// A vtable method handler for `Body` that lets `IsRead` methods through and +// puts every other method — call control, and anything added later — behind +// Authorize. +template +void Gated(GDBusConnection* conn, const gchar* sender, const gchar* path, const gchar* iface, const gchar* method, GVariant* params, GDBusMethodInvocation* inv, gpointer user) { + if (!AuthorizeCalls || IsRead(method)) { + Body(conn, sender, path, iface, method, params, inv, user); + return; + } + Authorize(inv, Body, DeniedError, Log); +} + +} // namespace imsd::bus diff --git a/packaging/APKBUILD b/packaging/APKBUILD index 0621cb7..ca32436 100644 --- a/packaging/APKBUILD +++ b/packaging/APKBUILD @@ -9,8 +9,9 @@ url="https://forgejo.catcrafts.net/Catcrafts/imsd" arch="aarch64 x86_64" license="GPL-3.0-only" # the media leg dlopen's the AMR-WB codecs; pw-record/pw-play drive PipeWire — -# none of which abuild's .so auto-scan can see -depends="modemmanager opencore-amr vo-amrwbenc pipewire-tools" +# none of which abuild's .so auto-scan can see; polkit answers the daemons' +# call-control check (ImsdBus) +depends="modemmanager opencore-amr vo-amrwbenc pipewire-tools polkit" # clang/libc++ C++26 modules build (see Makefile); llvm-runtimes ships the # libc++ std module sources (/usr/share/libc++/v1/std.cppm) it precompiles makedepends="clang lld libc++-dev llvm-libunwind-dev llvm-runtimes glib-dev pkgconf" diff --git a/packaging/APKBUILD.binary b/packaging/APKBUILD.binary index 8582c39..333529e 100644 --- a/packaging/APKBUILD.binary +++ b/packaging/APKBUILD.binary @@ -17,8 +17,9 @@ pkgdesc="Userspace IMS/VoLTE daemon for mainline Linux phones" url="https://forgejo.catcrafts.net/Catcrafts/imsd" arch="aarch64" license="GPL-3.0-only" -# the media leg dlopen's the AMR-WB codecs; pw-record/pw-play drive PipeWire -depends="modemmanager libc++ opencore-amr vo-amrwbenc pipewire-tools" +# the media leg dlopen's the AMR-WB codecs; pw-record/pw-play drive PipeWire; +# polkit answers the daemons' call-control check (ImsdBus) +depends="modemmanager libc++ opencore-amr vo-amrwbenc pipewire-tools polkit" # versioned provides = imsd satisfies soc-qcom-modem's 81voltd dependency AND # excludes the real package: 81voltd serves the modem's own ims-PDN requests, # which races imsd for the PDN and flaps it with a new prefix every ~2.5 min @@ -52,6 +53,8 @@ package() { "$pkgdir"/etc/xdg/autostart/imsd-dialerd.desktop install -Dm644 net.catcrafts.IMS1.conf \ "$pkgdir"/usr/share/dbus-1/system.d/net.catcrafts.IMS1.conf + install -Dm644 net.catcrafts.IMS1.policy \ + "$pkgdir"/usr/share/polkit-1/actions/net.catcrafts.IMS1.policy # the skel override hides kde-telephony's modem daemon autostart for the # account created at install — imsd-dialerd owns those session bus names install -Dm644 "$srcdir"/org.kde.modem.daemon.desktop \ diff --git a/packaging/imsd-ofonod.service b/packaging/imsd-ofonod.service index c8eb4a3..5229943 100644 --- a/packaging/imsd-ofonod.service +++ b/packaging/imsd-ofonod.service @@ -3,9 +3,11 @@ [Unit] Description=oFono-compatible GNOME Calls backend for imsd Documentation=https://forgejo.catcrafts.net/Catcrafts/imsd -# org.ofono is a system-bus name, so this is a system service, and it runs -# as root: its start hook restarts GNOME Calls in the users' own managers. -# Only enable it on a GNOME/Phosh session +# org.ofono is a system-bus name only root may own (org.ofono.conf), so this +# is a system service run as root: imsd admits the calls it forwards as +# root's (it has asked polkit about its own caller), and its start hook +# restarts GNOME Calls in the users' own managers. Only enable it on a +# GNOME/Phosh session # — Plasma Mobile uses imsd-dialerd instead. Both may run at once (different # buses, different consumers), but a real ofonod must not. Conflicts=ofono.service diff --git a/packaging/make-bin-tarball.sh b/packaging/make-bin-tarball.sh index 1d44451..3e695ec 100755 --- a/packaging/make-bin-tarball.sh +++ b/packaging/make-bin-tarball.sh @@ -29,6 +29,7 @@ cp packaging/ims-pdn-up.sh packaging/imsd-handover.sh packaging/imsd.service \ packaging/imsd-ofono-restart-calls.sh \ packaging/org.ofono.conf packaging/90_imsd-ofono.gschema.override \ packaging/80-imsd-ofono.preset \ - packaging/net.catcrafts.IMS1.conf "$stage/imsd-$VER/" + packaging/net.catcrafts.IMS1.conf packaging/net.catcrafts.IMS1.policy \ + "$stage/imsd-$VER/" tar -C "$stage" -czf "imsd-$VER.tar.gz" "imsd-$VER" echo "wrote imsd-$VER.tar.gz ($(du -h "imsd-$VER.tar.gz" | cut -f1))" diff --git a/packaging/net.catcrafts.IMS1.conf b/packaging/net.catcrafts.IMS1.conf index c365a8a..cd1c18b 100644 --- a/packaging/net.catcrafts.IMS1.conf +++ b/packaging/net.catcrafts.IMS1.conf @@ -11,12 +11,13 @@ - - + + - - - diff --git a/packaging/net.catcrafts.IMS1.policy b/packaging/net.catcrafts.IMS1.policy new file mode 100644 index 0000000..181972d --- /dev/null +++ b/packaging/net.catcrafts.IMS1.policy @@ -0,0 +1,26 @@ + + + + + + + Catcrafts + https://forgejo.catcrafts.net/Catcrafts/imsd + + Place, answer and end voice calls over IMS + System policy prevents voice calls. + + no + no + yes + + + diff --git a/packaging/org.ofono.conf b/packaging/org.ofono.conf index 0b73669..23a7cf0 100644 --- a/packaging/org.ofono.conf +++ b/packaging/org.ofono.conf @@ -15,20 +15,20 @@ + every name the receiver owns (dbus-daemon does not). The default + below allows sending too since 0.3.10; this line stays so that a + tighter default can never again silently drop every CallAdded / + CallStateChanged from imsd on the way to imsd-ofonod, as the old + default deny did (found on the FP6, 2026-09-20). --> - - + + + - - - - diff --git a/project.cpp b/project.cpp index 3507130..6e53866 100644 --- a/project.cpp +++ b/project.cpp @@ -57,6 +57,24 @@ static void ApplyGioFlags(Configuration& cfg) { } } +// ImsdBus — the polkit gate imsd and imsd-ofonod put in front of call +// control. A static library of its own: GLib, so outside imsd-core. +static std::unique_ptr BusLibrary(std::span args) { + auto bus = std::make_unique(); + bus->path = "./"; + bus->name = "imsd-bus"; + bus->outputName = "imsd-bus"; + ApplyStandardArgs(*bus, args); + bus->type = ConfigurationType::LibraryStatic; + { + std::array ifaces = { "interfaces/ImsdBus" }; + std::array impls = {}; + bus->GetInterfacesAndImplementations(ifaces, impls); + } + ApplyGioFlags(*bus); + return bus; +} + extern "C" Configuration CrafterBuildProject(std::span args) { // Four executables come out of this repo; which one a build produces is // chosen by `--product=`: @@ -70,7 +88,8 @@ extern "C" Configuration CrafterBuildProject(std::span a // (pure GDBus translation, no core). // ofonod — imsd-ofonod, the system-bus GNOME Calls backend // (org.ofono.*) bridging to imsd, for Phosh and - // GNOME Mobile (pure GDBus translation, no core). + // GNOME Mobile (GDBus translation plus the imsd-bus + // polkit gate on its callers, no core). bool wantMedia = false; bool wantDialerd = false; bool wantOfonod = false; @@ -86,6 +105,8 @@ extern "C" Configuration CrafterBuildProject(std::span a ofonod.outputName = "imsd-ofonod"; ApplyStandardArgs(ofonod, args); ofonod.type = ConfigurationType::Executable; + static auto Bus = BusLibrary(args); + ofonod.dependencies = { Bus.get() }; { std::array ifaces = {}; std::array impls = { "implementations/ofonod" }; @@ -161,7 +182,8 @@ extern "C" Configuration CrafterBuildProject(std::span a cfg.outputName = "imsd"; ApplyStandardArgs(cfg, args); cfg.type = ConfigurationType::Executable; - cfg.dependencies = { Core.get() }; + static auto Bus = BusLibrary(args); + cfg.dependencies = { Core.get(), Bus.get() }; { std::array ifaces = {}; std::array impls = { "implementations/main" };