rightkit_agent_tools/peer.rs
1//! The verified executable of the process that called a service method.
2//!
3//! A host that shows a consent prompt ("this program wants to drive CutRight") needs the
4//! caller's executable, not the label the caller declared in `hello`. `rightkit-daemon`
5//! (`CallContext::verify_peer_executable`) answers "is the caller this program?" but not "which
6//! program is it?". [`CallContextExt::peer_executable`] answers the second question from the same
7//! OS check, so no app has to probe with an impossible path and read the error.
8
9use rightkit_daemon::{CallContext, HostExpectation, HostIdentityReason};
10use std::path::PathBuf;
11
12/// A path no file can have (a NUL byte is not valid in a file name on any supported platform), so
13/// an expectation naming it never matches and the identity check always ends in a mismatch that
14/// reports what the OS found.
15const NO_SUCH_EXECUTABLE: &str = "\0rightkit-peer-executable-probe";
16
17/// Adds [`peer_executable`](CallContextExt::peer_executable) to the daemon's [`CallContext`].
18pub trait CallContextExt {
19 /// The executable of the process on the other end of this connection, as the OS reports it
20 /// and the transport verified it: the caller is a local app, its pid, uid or SID, and start
21 /// time are the ones recorded at accept (so a reused pid is refused), and the connection is
22 /// still open. Canonicalised when the file still exists.
23 ///
24 /// `None` fails closed: a device caller (no OS peer), a peer whose pid or executable cannot
25 /// be established, a process that exited or changed while it was identified, a platform that
26 /// cannot read a process's executable. Never a guess.
27 ///
28 /// This is a fact for display and audit (a consent prompt, a log line). It is not an
29 /// authorisation: to gate a method on a program use
30 /// `CallContext::verify_peer_executable`, which compares against the program you expect.
31 /// Run it per call; do not cache the answer past the connection.
32 fn peer_executable(&self) -> Option<PathBuf>;
33}
34
35impl CallContextExt for CallContext {
36 fn peer_executable(&self) -> Option<PathBuf> {
37 let probe = HostExpectation::executable(NO_SUCH_EXECUTABLE);
38 match self.verify_peer_executable(&probe) {
39 // Cannot happen (nothing matches the probe); were it ever so, the process is still
40 // the verified one.
41 Ok(host) => Some(host.executable),
42 // Only a mismatch means every identity check passed and the executable was read.
43 Err(e) if e.reason == HostIdentityReason::ExecutableMismatch => e.found_executable,
44 Err(_) => None,
45 }
46 }
47}