Skip to main content

crabshell_services/
polkit.rs

1//! A polkit authentication agent, as Quickshell's `PolkitAgent`: when a
2//! program asks polkit for something only some users may do, polkit asks
3//! the shell, and a config shows the "authenticate to do X" dialog.
4//!
5//! The shell registers with polkit on the system bus the first time a
6//! config asks for the agent. zbus has no object server here, so polkit's
7//! calls are answered by hand on the main thread. Each try runs polkit's
8//! setuid helper, which asks PAM: its prompts go into the request's state
9//! and the config's answers into its input. Nothing polls: the shell
10//! wakes when polkit calls or the helper prints.
11
12mod helper;
13#[cfg(test)]
14mod tests;
15
16use std::cell::{Cell, RefCell};
17use std::collections::{HashMap, VecDeque};
18use std::future::poll_fn;
19use std::pin::Pin;
20use std::rc::Rc;
21use std::sync::Once;
22
23use calloop::Interest;
24use crabshell_reactive::{ReadSignal, Signal};
25use crabshell_ui::Cx;
26use zbus::export::futures_core::Stream;
27use zbus::message::{Flags, Type as Kind};
28use zbus::{Connection, Message, MessageStream, OwnedVariant, Variant};
29
30use self::helper::{Helper, Line, Secret};
31use crate::Shared;
32use crate::dbus::{CALL_TIMEOUT, bus_call, owner_changed, owner_rule, set_changed};
33
34const AUTHORITY: &str = "org.freedesktop.PolicyKit1";
35const AUTHORITY_PATH: &str = "/org/freedesktop/PolicyKit1/Authority";
36const AUTHORITY_INTERFACE: &str = "org.freedesktop.PolicyKit1.Authority";
37const AGENT_INTERFACE: &str = "org.freedesktop.PolicyKit1.AuthenticationAgent";
38/// Where the agent answers polkit: any path of the shell's own.
39const AGENT_PATH: &str = "/org/crabshell/PolkitAgent";
40
41/// Where polkit's helper is on most distributions.
42/// shortcut: one path, so a distribution that keeps it in
43/// `/usr/libexec` gets no dialogs; look in both if one turns up.
44pub(crate) const HELPER: &str = "/usr/lib/polkit-1/polkit-agent-helper-1";
45
46/// Where polkit 127 and later listen for agents, and systemd starts the
47/// helper as root for each connection, so `HELPER` needn't be setuid.
48pub(crate) const HELPER_SOCKET: &str = "/run/polkit/agent-helper.socket";
49
50const FAILED: &str = "org.freedesktop.PolicyKit1.Error.Failed";
51const CANCELLED: &str = "org.freedesktop.PolicyKit1.Error.Cancelled";
52const ACCESS_DENIED: &str = "org.freedesktop.DBus.Error.AccessDenied";
53const INVALID_ARGS: &str = "org.freedesktop.DBus.Error.InvalidArgs";
54const UNKNOWN_METHOD: &str = "org.freedesktop.DBus.Error.UnknownMethod";
55
56/// The most bytes kept of a string from polkit or its helper.
57const MAX_TEXT: usize = 4 * 1024;
58/// The most wrong answers to one request before polkit is told it failed.
59const MAX_TRIES: u32 = 3;
60/// The most identities kept of a request.
61const MAX_IDENTITIES: usize = 64;
62/// The most requests waiting behind the one answered.
63const MAX_QUEUED: usize = 8;
64/// The most info and error lines a request keeps, the newest.
65const MAX_LINES: usize = 16;
66
67/// A request from polkit: a program asked to do `action_id`, and polkit
68/// wants a user to prove they may.
69#[derive(Clone, Debug, PartialEq)]
70pub struct AuthRequest {
71  /// Which request this is, so two alike still differ. polkit's cookie
72  /// stays in the agent.
73  id: u64,
74  /// What was asked for, such as `org.freedesktop.login1.reboot`.
75  pub action_id: String,
76  /// What to tell the user, in the shell's language, at most 4 KiB.
77  pub message: String,
78  /// A themed icon for the action, or empty.
79  pub icon_name: String,
80  /// The users who may answer, by name. The first is the one asked now;
81  /// [`PolkitAgent::pick_identity`] puts another first.
82  pub identities: Vec<String>,
83  /// Where the conversation is.
84  pub state: AuthState,
85  /// What PAM told the user this try, such as to touch a key, oldest
86  /// first. At most 16 lines of 4 KiB.
87  pub info: Vec<String>,
88  /// What PAM said went wrong this try, oldest first, kept as `info` is.
89  pub errors: Vec<String>,
90}
91
92/// Where a request's conversation is.
93#[derive(Clone, Debug, PartialEq)]
94pub enum AuthState {
95  /// polkit's helper is starting, or has only told the user something.
96  Waiting,
97  /// PAM asks `message`, such as `Password: `, for
98  /// [`PolkitAgent::respond`]. `visible` is whether the answer may show
99  /// as it is typed.
100  Asking {
101    /// The question, at most 4 KiB.
102    message: String,
103    /// `false` for a password.
104    visible: bool,
105  },
106  /// The answer is being checked.
107  Busy,
108  /// The answer was wrong. [`PolkitAgent::respond`] tries again; after
109  /// the third failure polkit is told, and the request goes.
110  Failed {
111    /// What PAM said, or a plain "Authentication failed".
112    message: String,
113  },
114}
115
116/// The shell's polkit agent, from `cx.polkit_agent()`. A clone is cheap
117/// and shares the one agent.
118#[derive(Clone)]
119pub struct PolkitAgent {
120  /// `None` when the shell provided no services.
121  service: Option<Rc<Service>>,
122  request: ReadSignal<Option<AuthRequest>>,
123}
124
125impl PolkitAgent {
126  /// The request being answered now, or `None`. One shows at a time; up
127  /// to 8 more wait their turn, and polkit is told any past that failed.
128  pub fn request(&self) -> ReadSignal<Option<AuthRequest>> {
129    self.request
130  }
131
132  /// Answers the request's question, as the user typed it. After a
133  /// failure, it tries again and answers the new try's first question.
134  /// At any other time, or with a line break in it, it is dropped. It is
135  /// never logged, and its bytes are zeroed once sent.
136  pub fn respond(&self, text: impl Into<String>) {
137    let secret = Secret(text.into().into_bytes());
138    if let Some(service) = &self.service {
139      service.respond(secret);
140    }
141  }
142
143  /// Gives up on the request, as the user's cancel button does, and tells
144  /// polkit so.
145  pub fn cancel(&self) {
146    if let Some(service) = &self.service {
147      service.finish(Some((CANCELLED, "the user cancelled")));
148    }
149  }
150
151  /// Asks `name`, one of the request's identities, instead: it moves to
152  /// the front, and the conversation starts again as that user. An
153  /// unknown name is logged.
154  pub fn pick_identity(&self, name: &str) {
155    if let Some(service) = &self.service {
156      service.pick(name);
157    }
158  }
159}
160
161/// `cx.polkit_agent()`.
162pub(crate) fn polkit_agent(cx: &Cx) -> PolkitAgent {
163  let Some(service) = cx.service::<Service>() else {
164    static LOGGED: Once = Once::new();
165    LOGGED.call_once(|| {
166      tracing::warn!("cx.polkit_agent() needs the services the shell provides; no requests arrive");
167    });
168    return PolkitAgent {
169      service: None,
170      request: cx.signal(None).read_only(),
171    };
172  };
173  let request = service.start();
174  PolkitAgent {
175    service: Some(service),
176    request,
177  }
178}
179
180/// The polkit agent of one shell, started on first use.
181pub(crate) struct Service {
182  shared: Rc<Shared>,
183  state: RefCell<Option<State>>,
184  /// The number the next helper run and request get.
185  next: Cell<u64>,
186}
187
188struct State {
189  request: Signal<Option<AuthRequest>>,
190  /// The system bus, once connected.
191  connection: Option<Connection>,
192  /// polkit's unique name, once it took the registration: the only
193  /// caller the agent answers.
194  authority: Option<String>,
195  current: Option<Current>,
196  /// Requests that came while one was answered, oldest first.
197  queue: VecDeque<Begin>,
198}
199
200/// Stops the helper and forgets the requests when the root goes. The
201/// agent's connection closes at the next message on it, or with the shell.
202struct StopOnDrop(Rc<Service>);
203
204impl Drop for StopOnDrop {
205  fn drop(&mut self) {
206    self.0.forget();
207  }
208}
209
210/// A `BeginAuthentication` call, kept until it is answered.
211struct Begin {
212  call: Message,
213  cookie: String,
214  shown: AuthRequest,
215}
216
217/// The request being answered.
218struct Current {
219  begin: Begin,
220  failures: u32,
221  helper: Option<Helper>,
222  /// An answer given after a failure, for the next try's first question.
223  answer: Option<Secret>,
224}
225
226impl Current {
227  fn new(begin: Begin) -> Current {
228    Current {
229      begin,
230      failures: 0,
231      helper: None,
232      answer: None,
233    }
234  }
235}
236
237impl Service {
238  pub(crate) fn new(shared: Rc<Shared>) -> Service {
239    Service {
240      shared,
241      state: RefCell::new(None),
242      next: Cell::new(1),
243    }
244  }
245
246  fn next(&self) -> u64 {
247    let next = self.next.get();
248    self.next.set(next + 1);
249    next
250  }
251
252  /// The request, registering with polkit if this is the first ask.
253  fn start(self: &Rc<Self>) -> ReadSignal<Option<AuthRequest>> {
254    if let Some(state) = &*self.state.borrow() {
255      return state.request.read_only();
256    }
257    let request = self.shared.root.signal(None);
258    *self.state.borrow_mut() = Some(State {
259      request,
260      connection: None,
261      authority: None,
262      current: None,
263      queue: VecDeque::new(),
264    });
265    self.shared.root.signal(StopOnDrop(Rc::clone(self)));
266    match self.shared.config.system_bus.clone() {
267      Some(address) => self.shared.spawn(run(Rc::clone(self), address)),
268      None => tracing::info!("no system bus, so no polkit requests arrive"),
269    }
270    request.read_only()
271  }
272
273  /// Drops the connection and every request unanswered, as when the root
274  /// or the bus goes. Touches no signal, since the root may be going.
275  fn forget(&self) {
276    if let Some(state) = self.state.borrow_mut().as_mut() {
277      state.connection = None;
278    }
279    self.drop_requests();
280  }
281
282  /// Drops every request unanswered, stopping the helper, as when polkit
283  /// goes and nobody could answer. Touches no signal.
284  fn drop_requests(&self) {
285    let current = self.state.borrow_mut().as_mut().and_then(|state| {
286      state.queue.clear();
287      state.authority = None;
288      state.current.take()
289    });
290    if let Some(helper) = current.and_then(|current| current.helper) {
291      helper.stop(&self.shared, true);
292    }
293  }
294
295  /// Whether the request is still there: its owner, the root, isn't
296  /// disposed.
297  fn alive(&self) -> bool {
298    let state = self.state.borrow();
299    let alive = state
300      .as_ref()
301      .and_then(|state| state.request.try_with(|_| ()));
302    alive.is_some()
303  }
304
305  /// Runs `f` on the request being answered, if there is one.
306  fn with_current<R>(&self, f: impl FnOnce(&mut Current) -> R) -> Option<R> {
307    let mut state = self.state.borrow_mut();
308    state.as_mut()?.current.as_mut().map(f)
309  }
310
311  /// Shows the request as it is now.
312  fn publish(&self) {
313    let shown = self.state.borrow().as_ref().map(|state| {
314      let current = state.current.as_ref();
315      (
316        state.request,
317        current.map(|current| current.begin.shown.clone()),
318      )
319    });
320    if let Some((signal, shown)) = shown {
321      set_changed(signal, shown);
322    }
323  }
324
325  /// One method call from the bus.
326  fn on_call(self: &Rc<Self>, call: Message) {
327    let header = call.header();
328    let sender = header.sender().map(|sender| sender.as_str());
329    let authority = self
330      .state
331      .borrow()
332      .as_ref()
333      .and_then(|state| state.authority.clone());
334    if sender.is_none() || sender != authority.as_deref() {
335      tracing::warn!(
336        ?sender,
337        "a program other than polkit called the agent; refused"
338      );
339      return self.reply(
340        &call,
341        Some((ACCESS_DENIED, "only polkit may call the agent")),
342      );
343    }
344    let path = header.path().map(|path| path.as_str());
345    let interface = header.interface().map(|interface| interface.as_str());
346    let member = header.member().map(|member| member.as_str());
347    match (path, interface, member) {
348      (Some(AGENT_PATH), Some(AGENT_INTERFACE), Some("BeginAuthentication")) => self.begin(call),
349      (Some(AGENT_PATH), Some(AGENT_INTERFACE), Some("CancelAuthentication")) => {
350        self.cancelled(call);
351      }
352      _ => self.reply(&call, Some((UNKNOWN_METHOD, "no such method"))),
353    }
354  }
355
356  /// polkit asks the user to authenticate.
357  fn begin(self: &Rc<Self>, call: Message) {
358    let begin = match begin_args(&call, self.next()) {
359      Ok(begin) => begin,
360      Err((error, why)) => {
361        tracing::warn!(why, "a polkit request the agent can't answer");
362        return self.reply(&call, Some((error, why)));
363      }
364    };
365    let refused = {
366      let mut state = self.state.borrow_mut();
367      let Some(state) = state.as_mut() else {
368        return;
369      };
370      let action = &begin.shown.action_id;
371      tracing::info!(action, "polkit asks to authenticate");
372      if state.current.is_none() {
373        state.current = Some(Current::new(begin));
374        None
375      } else if state.queue.len() < MAX_QUEUED {
376        state.queue.push_back(begin);
377        return;
378      } else {
379        Some(begin)
380      }
381    };
382    match refused {
383      Some(begin) => {
384        tracing::warn!("{MAX_QUEUED} polkit requests wait already; refused another");
385        self.reply(&begin.call, Some((FAILED, "too many requests wait")));
386      }
387      None => self.run_helper(),
388    }
389  }
390
391  /// polkit takes back the request `cookie` names.
392  fn cancelled(self: &Rc<Self>, call: Message) {
393    let Ok(cookie) = call.body().deserialize::<String>() else {
394      return self.reply(&call, Some((INVALID_ARGS, "not a cookie")));
395    };
396    self.reply(&call, None);
397    let why = "polkit cancelled it";
398    let current = self.with_current(|current| current.begin.cookie == cookie);
399    if current == Some(true) {
400      tracing::info!("polkit cancelled the request");
401      return self.finish(Some((CANCELLED, why)));
402    }
403    let queued = self.state.borrow_mut().as_mut().and_then(|state| {
404      let at = state
405        .queue
406        .iter()
407        .position(|begin| begin.cookie == cookie)?;
408      state.queue.remove(at)
409    });
410    if let Some(begin) = queued {
411      self.reply(&begin.call, Some((CANCELLED, why)));
412    }
413  }
414
415  /// The config picks identity `name`.
416  fn pick(self: &Rc<Self>, name: &str) {
417    let picked = self.with_current(|current| {
418      let identities = &mut current.begin.shown.identities;
419      let at = identities.iter().position(|known| known == name)?;
420      identities[..=at].rotate_right(1);
421      current.answer = None;
422      Some(current.helper.take())
423    });
424    let Some(helper) = picked.flatten() else {
425      return tracing::warn!(name, "no such identity to pick for polkit");
426    };
427    if let Some(helper) = helper {
428      helper.stop(&self.shared, true);
429    }
430    self.run_helper();
431  }
432
433  /// Starts a try: polkit's helper, for the first identity.
434  fn run_helper(self: &Rc<Self>) {
435    let Some(event_loop) = self.shared.event_loop() else {
436      return;
437    };
438    let run = self.next();
439    let started = self.with_current(|current| {
440      let shown = &mut current.begin.shown;
441      shown.state = AuthState::Waiting;
442      shown.info.clear();
443      shown.errors.clear();
444      let config = &self.shared.config;
445      let user = &shown.identities[0];
446      let mut helper = Helper::start(&config.polkit_helper, &config.polkit_socket, user, run)?;
447      if let Err(error) = helper.send(current.begin.cookie.as_bytes()) {
448        helper.stop(&self.shared, true);
449        return Err(error);
450      }
451      let service = Rc::clone(self);
452      let ready = Box::new(move || service.on_output(run));
453      helper.token = helper
454        .stdout_fd()
455        .and_then(|fd| event_loop.insert_fd(fd, Interest::READ, ready));
456      if helper.token.is_none() {
457        helper.stop(&self.shared, true);
458        return Err(std::io::Error::other("its output couldn't be watched"));
459      }
460      current.helper = Some(helper);
461      Ok(())
462    });
463    match started {
464      Some(Ok(())) => self.publish(),
465      Some(Err(error)) => {
466        let program = self.shared.config.polkit_helper.display();
467        tracing::warn!(%error, %program, "couldn't start polkit's helper");
468        self.finish(Some((FAILED, "polkit's helper couldn't start")));
469      }
470      None => {}
471    }
472  }
473
474  /// What the helper of run `run` printed.
475  fn on_output(self: &Rc<Self>, run: u64) {
476    let running = |service: &Service| {
477      let running =
478        service.with_current(|current| current.helper.as_ref().map(|helper| helper.run));
479      running.flatten() == Some(run)
480    };
481    if !running(self) {
482      return;
483    }
484    let read = self.with_current(|current| current.helper.as_mut()?.read());
485    let Some((lines, ended)) = read.flatten() else {
486      return;
487    };
488    for line in lines {
489      if !running(self) {
490        return;
491      }
492      self.on_line(helper::parse(&line));
493    }
494    if ended && running(self) {
495      tracing::warn!("polkit's helper ended without an answer");
496      self.failed();
497    }
498  }
499
500  fn on_line(self: &Rc<Self>, line: Line) {
501    let keep = |lines: &mut Vec<String>, text| {
502      if lines.len() == MAX_LINES {
503        lines.remove(0);
504      }
505      lines.push(text);
506    };
507    let verdict = line_verdict(&line);
508    let answer = self.with_current(|current| {
509      let shown = &mut current.begin.shown;
510      match line {
511        Line::Prompt { message, visible } => {
512          shown.state = AuthState::Asking { message, visible };
513          return current.answer.take();
514        }
515        Line::Info(text) => keep(&mut shown.info, text),
516        Line::Error(text) => keep(&mut shown.errors, text),
517        Line::Unknown => tracing::debug!("polkit's helper printed a line it doesn't know; skipped"),
518        Line::Success | Line::Failure => {}
519      }
520      None
521    });
522    match verdict {
523      Some(true) => {
524        tracing::info!("authenticated for polkit");
525        self.finish(None);
526      }
527      Some(false) => self.failed(),
528      None => match answer.flatten() {
529        // A try after a failure answers its first question at once.
530        Some(secret) => self.send(secret),
531        None => self.publish(),
532      },
533    }
534  }
535
536  /// A wrong answer, or a helper that ended without saying: shows it, or
537  /// after the third tells polkit.
538  fn failed(self: &Rc<Self>) {
539    let gave_up = self.with_current(|current| {
540      if let Some(helper) = current.helper.take() {
541        helper.stop(&self.shared, true);
542      }
543      current.answer = None;
544      current.failures += 1;
545      let shown = &mut current.begin.shown;
546      let message = shown.errors.last().cloned();
547      let message = message.unwrap_or_else(|| String::from("Authentication failed"));
548      shown.state = AuthState::Failed { message };
549      current.failures >= MAX_TRIES
550    });
551    match gave_up {
552      Some(true) => {
553        tracing::info!("polkit authentication failed {MAX_TRIES} times");
554        self.finish(Some((FAILED, "authentication failed")));
555      }
556      Some(false) => self.publish(),
557      None => {}
558    }
559  }
560
561  /// The config's answer.
562  fn respond(self: &Rc<Self>, secret: Secret) {
563    // A line break would end the line early, and the helper, in C, would
564    // read a NUL as the end.
565    if secret.0.contains(&b'\n') || secret.0.contains(&0) {
566      return tracing::warn!("a polkit answer with a line break or NUL was dropped");
567    }
568    let state = self.with_current(|current| current.begin.shown.state.clone());
569    match state {
570      Some(AuthState::Asking { .. }) => self.send(secret),
571      Some(AuthState::Failed { .. }) => {
572        self.with_current(|current| current.answer = Some(secret));
573        self.run_helper();
574      }
575      _ => tracing::debug!("no polkit question to answer; an answer was dropped"),
576    }
577  }
578
579  /// Sends `secret` to the helper, which then checks it. It goes when
580  /// this returns.
581  fn send(self: &Rc<Self>, secret: Secret) {
582    let sent = self.with_current(|current| {
583      let sent = current.helper.as_mut().map(|helper| helper.send(&secret.0));
584      if let Some(Ok(())) = sent {
585        current.begin.shown.state = AuthState::Busy;
586      }
587      sent
588    });
589    match sent.flatten() {
590      Some(Ok(())) => self.publish(),
591      Some(Err(error)) => {
592        tracing::warn!(%error, "couldn't answer polkit's helper");
593        self.failed();
594      }
595      None => {}
596    }
597  }
598
599  /// Ends the request being answered and tells polkit: `None` for
600  /// authenticated, else the error's name and why. The next waiting
601  /// request starts.
602  fn finish(self: &Rc<Self>, error: Option<(&'static str, &str)>) {
603    let current = self.state.borrow_mut().as_mut().and_then(|state| {
604      let current = state.current.take()?;
605      state.current = state.queue.pop_front().map(Current::new);
606      Some(current)
607    });
608    let Some(current) = current else {
609      return;
610    };
611    if let Some(helper) = current.helper {
612      // After a success it is telling polkit, so it is left to end.
613      helper.stop(&self.shared, error.is_some());
614    }
615    self.reply(&current.begin.call, error);
616    match self.with_current(|_| ()) {
617      Some(()) => self.run_helper(),
618      None => self.publish(),
619    }
620  }
621
622  /// Answers `call`: with an empty return for `None`, else with the error
623  /// and why.
624  fn reply(&self, call: &Message, error: Option<(&str, &str)>) {
625    let header = call.header();
626    if header.primary().flags().contains(Flags::NO_REPLY_EXPECTED) {
627      return;
628    }
629    let reply = match error {
630      None => Message::method_return(&header).build(&()),
631      Some((name, why)) => Message::error(&header, name).build(&why),
632    };
633    let connection = self
634      .state
635      .borrow()
636      .as_ref()
637      .and_then(|state| state.connection.clone());
638    let Some(connection) = connection else {
639      return;
640    };
641    self.shared.spawn(async move {
642      let sent = match reply {
643        Ok(reply) => connection.send(&reply).await,
644        Err(error) => Err(error),
645      };
646      if let Err(error) = sent {
647        tracing::debug!(%error, "the polkit agent's answer wasn't sent");
648      }
649    });
650  }
651}
652
653/// Whether `line` ends the try: `true` for a success, `false` for a
654/// failure.
655fn line_verdict(line: &Line) -> Option<bool> {
656  match line {
657    Line::Success => Some(true),
658    Line::Failure => Some(false),
659    _ => None,
660  }
661}
662
663/// `BeginAuthentication`'s arguments: the action, the message, the icon,
664/// details, the cookie and the identities.
665type BeginArgs<'a> = (
666  &'a str,
667  &'a str,
668  &'a str,
669  HashMap<&'a str, &'a str>,
670  &'a str,
671  Vec<(&'a str, HashMap<&'a str, OwnedVariant>)>,
672);
673
674/// The request `call` asks for, numbered `id`, or the error to answer.
675fn begin_args(call: &Message, id: u64) -> Result<Begin, (&'static str, &'static str)> {
676  let body = call.body();
677  let Ok((action_id, message, icon_name, _, cookie, identities)) =
678    body.deserialize::<BeginArgs<'_>>()
679  else {
680    return Err((INVALID_ARGS, "not a polkit request"));
681  };
682  // It is the helper's first line of input, so it can't hold another.
683  if cookie.is_empty() || cookie.len() > MAX_TEXT || cookie.contains('\n') {
684    return Err((FAILED, "a bad cookie"));
685  }
686  let passwd = std::fs::read_to_string("/etc/passwd").unwrap_or_default();
687  let mut users: Vec<(u32, String)> = identities
688    .iter()
689    .take(MAX_IDENTITIES)
690    .filter(|(kind, _)| *kind == "unix-user")
691    .filter_map(|(_, details)| u32::try_from(details.get("uid")?).ok())
692    .filter_map(|uid| Some((uid, user_name(&passwd, uid)?)))
693    .collect();
694  // The session's user is asked first: polkit may list root first, as for
695  // an admin group.
696  let own = rustix::process::getuid().as_raw();
697  if let Some(at) = users.iter().position(|(uid, _)| *uid == own) {
698    users[..=at].rotate_right(1);
699  }
700  let names: Vec<String> = users.into_iter().map(|(_, name)| name).collect();
701  if names.is_empty() {
702    return Err((FAILED, "no user the agent knows may answer"));
703  }
704  Ok(Begin {
705    call: call.clone(),
706    cookie: cookie.to_owned(),
707    shown: AuthRequest {
708      id,
709      action_id: cut(action_id, MAX_TEXT),
710      message: cut(message, MAX_TEXT),
711      icon_name: cut(icon_name, MAX_TEXT),
712      identities: names,
713      state: AuthState::Waiting,
714      info: Vec::new(),
715      errors: Vec::new(),
716    },
717  })
718}
719
720/// `text`'s first `max` bytes at most, cut at a char boundary.
721fn cut(text: &str, max: usize) -> String {
722  text[..text.floor_char_boundary(max)].to_owned()
723}
724
725/// The name of user `uid`, from `passwd`, the text of `/etc/passwd`, or
726/// for this shell's own user from `USER`.
727/// shortcut: reads only `/etc/passwd`, so another user from LDAP or
728/// systemd-homed isn't offered; ask NSS through a crate if that matters.
729fn user_name(passwd: &str, uid: u32) -> Option<String> {
730  let listed = passwd.lines().find_map(|line| {
731    let mut fields = line.split(':');
732    let name = fields.next()?;
733    let uid_field = fields.nth(1)?;
734    (uid_field.parse() == Ok(uid)).then(|| name.to_owned())
735  });
736  let own = || {
737    let ours = rustix::process::getuid().as_raw() == uid;
738    ours.then(|| std::env::var("USER").ok()).flatten()
739  };
740  listed.or_else(own).filter(|name| !name.is_empty())
741}
742
743/// Who the agent answers for: this login session when `session` names
744/// one, as `XDG_SESSION_ID` does, else this process.
745fn subject(session: Option<String>) -> (&'static str, HashMap<&'static str, Variant<'static>>) {
746  match session {
747    Some(id) => (
748      "unix-session",
749      HashMap::from([("session-id", Variant::from(id))]),
750    ),
751    None => (
752      "unix-process",
753      HashMap::from([
754        ("pid", Variant::from(std::process::id())),
755        ("start-time", Variant::from(start_time().unwrap_or(0))),
756      ]),
757    ),
758  }
759}
760
761/// When this process started, in clock ticks after boot, as polkit names
762/// a process: the 22nd field of `/proc/self/stat`.
763fn start_time() -> Option<u64> {
764  let stat = std::fs::read_to_string("/proc/self/stat").ok()?;
765  // Fields after the name in parentheses start at the 3rd.
766  stat
767    .rsplit_once(')')?
768    .1
769    .split_whitespace()
770    .nth(19)?
771    .parse()
772    .ok()
773}
774
775/// Joins the system bus at `address`, registers the agent with polkit,
776/// again whenever polkit starts again, and answers polkit's calls, until
777/// the bus closes or the request is disposed.
778async fn run(service: Rc<Service>, address: zbus::Address) {
779  let connection = match zbus::connection::Builder::address(address).build().await {
780    Ok(connection) => connection,
781    Err(error) => {
782      return tracing::info!(%error, "can't reach the system bus, so no polkit requests arrive");
783    }
784  };
785  // Made before polkit can call, so no call is missed.
786  let mut messages = MessageStream::from(&connection);
787  if let Err(error) = bus_call(&connection, "AddMatch", &owner_rule(AUTHORITY)).await {
788    return tracing::warn!(%error, "can't follow polkit on the system bus, so no requests arrive");
789  }
790  if let Some(state) = service.state.borrow_mut().as_mut() {
791    state.connection = Some(connection.clone());
792  }
793  register(&service, &connection).await;
794  loop {
795    let next = poll_fn(|cx| Pin::new(&mut messages).poll_next(cx)).await;
796    if !service.alive() {
797      return;
798    }
799    match next {
800      Some(Ok(message)) if message.header().message_type() == Kind::MethodCall => {
801        service.on_call(message);
802      }
803      Some(Ok(message)) => {
804        // The bus daemon sends this, so nobody else can fake it.
805        if let Some((name, owner)) = owner_changed(&message)
806          && name == AUTHORITY
807        {
808          // Its requests went with it.
809          service.drop_requests();
810          service.publish();
811          if !owner.is_empty() {
812            tracing::info!("polkit started again; registering the agent with it");
813            register(&service, &connection).await;
814          }
815        }
816      }
817      Some(Err(error)) => tracing::warn!(%error, "a bad message on the system bus"),
818      None => break,
819    }
820  }
821  tracing::warn!("the system bus closed, so no more polkit requests arrive");
822  service.forget();
823  service.publish();
824}
825
826/// Registers the agent with the polkit on `connection`, which takes its
827/// calls only from the unique name that answered. A refusal is logged.
828async fn register(service: &Rc<Service>, connection: &Connection) {
829  let session = std::env::var("XDG_SESSION_ID")
830    .ok()
831    .filter(|id| !id.is_empty());
832  // The locale desktop entries are read in, so polkit's message matches.
833  let locale = service.shared.config.desktop_entries.locale.as_str();
834  let body = (subject(session), locale, AGENT_PATH);
835  let call = connection.call_method(
836    Some(AUTHORITY),
837    AUTHORITY_PATH,
838    Some(AUTHORITY_INTERFACE),
839    "RegisterAuthenticationAgent",
840    &body,
841  );
842  let authority = match service.shared.timeout(CALL_TIMEOUT, call).await {
843    Ok(reply) => reply.header().sender().map(|sender| sender.to_string()),
844    Err(error) => return tracing::warn!(%error, "polkit refused the agent, so no requests arrive"),
845  };
846  tracing::info!("registered as the polkit agent");
847  if let Some(state) = service.state.borrow_mut().as_mut() {
848    state.authority = authority;
849  }
850}