Skip to main content

crabshell_services/
greetd.rs

1//! greetd, the login manager, for a greeter config: a session is made for
2//! a user, its questions are answered, and it is started once the greeter
3//! exits. The protocol is greetd-ipc(7): over the socket `GREETD_SOCK`
4//! names, each message is its length as a native-endian `u32`, then that
5//! much JSON.
6
7#[cfg(test)]
8mod tests;
9
10use std::cell::RefCell;
11use std::collections::VecDeque;
12use std::io::{ErrorKind, Read, Write};
13use std::os::fd::AsFd;
14use std::os::unix::net::UnixStream;
15use std::rc::Rc;
16use std::sync::Once;
17
18use calloop::{Interest, RegistrationToken};
19use crabshell_reactive::{ReadSignal, Signal, untrack};
20use crabshell_ui::Cx;
21use serde::{Deserialize, Serialize};
22use serde_json::json;
23
24use crate::Shared;
25
26/// The largest message read from greetd; a larger one drops the
27/// connection.
28pub(crate) const MAX_MESSAGE: usize = 64 * 1024;
29
30/// The longest text kept from greetd, cut at a character.
31const MAX_TEXT: usize = 4 * 1024;
32
33/// The most informative messages kept; older ones go.
34pub(crate) const MAX_MESSAGES: usize = 64;
35
36/// Where a session being made stands.
37#[derive(Clone, Debug, Default, PartialEq)]
38pub enum GreetState {
39  /// No session is being made.
40  #[default]
41  Idle,
42  /// A request is on its way to greetd.
43  Busy,
44  /// greetd asks a question, such as for the password; `visible` is
45  /// false for a secret.
46  Asking {
47    /// The question, such as `Password:`.
48    message: String,
49    /// Whether the answer may show as it is typed.
50    visible: bool,
51  },
52  /// The user is authenticated, and the session can be started.
53  Authenticated,
54  /// The session will start once the greeter exits.
55  Started,
56  /// A request failed. `auth` says it was the credentials, which the
57  /// user can try again.
58  Failed {
59    /// greetd's description.
60    message: String,
61    /// Whether authentication failed, rather than something else.
62    auth: bool,
63  },
64}
65
66/// An informative or error message greetd sent while a session was made.
67#[derive(Clone, Debug, PartialEq)]
68pub struct GreetMessage {
69  /// The message.
70  pub text: String,
71  /// Whether it is an error, rather than information.
72  pub error: bool,
73}
74
75/// greetd, from `cx.greetd()`. A clone is cheap and shares it.
76///
77/// A greeter makes a session for a user with
78/// [`create_session`](Self::create_session), answers each question in
79/// [`state`](Self::state) with [`respond`](Self::respond), and once it is
80/// [`Authenticated`](GreetState::Authenticated) starts it with
81/// [`start_session`](Self::start_session), then exits so greetd can.
82#[derive(Clone)]
83pub struct Greetd {
84  /// `None` when the shell provided no services.
85  service: Option<Rc<Service>>,
86  state: ReadSignal<GreetState>,
87  messages: ReadSignal<Vec<GreetMessage>>,
88}
89
90impl Greetd {
91  /// Where the session being made stands.
92  pub fn state(&self) -> ReadSignal<GreetState> {
93    self.state
94  }
95
96  /// The informative and error messages greetd sent since the last
97  /// [`create_session`](Self::create_session), oldest first. They need no
98  /// answer: each is acknowledged as it comes.
99  pub fn messages(&self) -> ReadSignal<Vec<GreetMessage>> {
100    self.messages
101  }
102
103  /// Makes a session for `user` and starts authenticating them, ending
104  /// any session being made before.
105  pub fn create_session(&self, user: &str) {
106    if let Some(service) = &self.service {
107      service.create_session(user);
108    }
109  }
110
111  /// Answers greetd's question. Unless the state is
112  /// [`Asking`](GreetState::Asking), the answer is dropped and that is
113  /// logged. The answer is never logged, and its bytes are zeroed once
114  /// written.
115  pub fn respond(&self, answer: &str) {
116    if let Some(service) = &self.service {
117      service.respond(answer);
118    }
119  }
120
121  /// Starts the authenticated session with `command`, such as
122  /// `["sway"]`, and `env` lines such as `XDG_SESSION_TYPE=wayland`
123  /// added to what PAM sets. greetd joins `command` with spaces and runs
124  /// the line with sh(1), so an entry holding spaces or shell characters
125  /// needs quotes of its own, and one entry may be a whole shell line. It starts once the greeter exits. Unless the
126  /// state is [`Authenticated`](GreetState::Authenticated), nothing is
127  /// sent and that is logged.
128  pub fn start_session(&self, command: Vec<String>, env: Vec<String>) {
129    if let Some(service) = &self.service {
130      service.start(command, env);
131    }
132  }
133
134  /// Ends the session being made.
135  pub fn cancel(&self) {
136    if let Some(service) = &self.service {
137      service.send(
138        Kind::Cancel { asked: true },
139        json!({"type": "cancel_session"}),
140      );
141    }
142  }
143}
144
145/// `cx.greetd()`.
146pub(crate) fn greetd(cx: &Cx) -> Greetd {
147  let Some(service) = cx.service::<Service>() else {
148    static LOGGED: Once = Once::new();
149    LOGGED.call_once(|| {
150      tracing::warn!("cx.greetd() needs the services the shell provides; it never connects");
151    });
152    return Greetd {
153      service: None,
154      state: cx.signal(GreetState::Idle).read_only(),
155      messages: cx.signal(Vec::new()).read_only(),
156    };
157  };
158  let (state, messages) = service.signals();
159  Greetd {
160    service: Some(service),
161    state: state.read_only(),
162    messages: messages.read_only(),
163  }
164}
165
166/// greetd for one shell: its signals and connection, made on first use.
167pub(crate) struct Service {
168  shared: Rc<Shared>,
169  inner: RefCell<Inner>,
170}
171
172#[derive(Default)]
173struct Inner {
174  signals: Option<(Signal<GreetState>, Signal<Vec<GreetMessage>>)>,
175  connection: Option<Connection>,
176  /// Requests waiting their turn: greetd answers one at a time.
177  queue: VecDeque<(Kind, Vec<u8>)>,
178  /// The request greetd is answering.
179  asked: Option<Kind>,
180  /// Whether greetd holds a session made here, which a new one must
181  /// cancel first.
182  session: bool,
183}
184
185struct Connection {
186  stream: UnixStream,
187  reader: RegistrationToken,
188  writer: Option<RegistrationToken>,
189  inbox: Vec<u8>,
190  /// The request being written, zeroed once all of it went.
191  outbox: Vec<u8>,
192  sent: usize,
193}
194
195/// What a request was, to read its answer.
196#[derive(Clone, Copy, Debug, PartialEq)]
197enum Kind {
198  Create,
199  Respond,
200  Start,
201  /// `asked` when the config asked; otherwise it ends a session that
202  /// failed, and changes nothing shown.
203  Cancel {
204    asked: bool,
205  },
206}
207
208/// greetd's answer to a request.
209#[derive(Deserialize)]
210#[serde(tag = "type", rename_all = "snake_case")]
211enum Reply {
212  Success,
213  Error {
214    error_type: String,
215    description: String,
216  },
217  AuthMessage {
218    auth_message_type: String,
219    auth_message: String,
220  },
221}
222
223impl Service {
224  pub(crate) fn new(shared: Rc<Shared>) -> Service {
225    Service {
226      shared,
227      inner: RefCell::default(),
228    }
229  }
230
231  /// The signals, made under the root on first use.
232  fn signals(&self) -> (Signal<GreetState>, Signal<Vec<GreetMessage>>) {
233    let mut inner = self.inner.borrow_mut();
234    *inner.signals.get_or_insert_with(|| {
235      (
236        self.shared.root.signal(GreetState::Idle),
237        self.shared.root.signal(Vec::new()),
238      )
239    })
240  }
241
242  fn create_session(self: &Rc<Self>, user: &str) {
243    if self.inner.borrow().session {
244      // greetd makes one session at a time.
245      self.send(
246        Kind::Cancel { asked: false },
247        json!({"type": "cancel_session"}),
248      );
249    }
250    let (_, messages) = self.signals();
251    set(messages, Vec::new());
252    self.send(
253      Kind::Create,
254      json!({"type": "create_session", "username": user}),
255    );
256  }
257
258  fn respond(self: &Rc<Self>, answer: &str) {
259    if !matches!(self.state(), GreetState::Asking { .. }) {
260      tracing::warn!("greetd asked nothing, so the answer is dropped");
261      return;
262    }
263    self.queue(Kind::Respond, answer_frame(Some(answer)), false);
264  }
265
266  fn start(self: &Rc<Self>, command: Vec<String>, env: Vec<String>) {
267    if self.state() != GreetState::Authenticated {
268      tracing::warn!("a session starts only once authenticated; nothing is sent");
269      return;
270    }
271    self.send(
272      Kind::Start,
273      json!({"type": "start_session", "cmd": command, "env": env}),
274    );
275  }
276
277  fn send(self: &Rc<Self>, kind: Kind, body: serde_json::Value) {
278    self.queue(kind, frame(body.to_string().as_bytes()), false);
279  }
280
281  fn state(&self) -> GreetState {
282    let (state, _) = self.signals();
283    untrack(|| state.try_with(Clone::clone)).unwrap_or_default()
284  }
285
286  /// Queues a request, at the `front` for one that follows up greetd's
287  /// last answer.
288  fn queue(self: &Rc<Self>, kind: Kind, bytes: Vec<u8>, front: bool) {
289    let (state, _) = self.signals();
290    if kind != (Kind::Cancel { asked: false }) {
291      set(state, GreetState::Busy);
292    }
293    let mut inner = self.inner.borrow_mut();
294    match kind {
295      Kind::Create => inner.session = true,
296      Kind::Cancel { .. } => inner.session = false,
297      Kind::Respond | Kind::Start => {}
298    }
299    if front {
300      inner.queue.push_front((kind, bytes));
301    } else {
302      inner.queue.push_back((kind, bytes));
303    }
304    drop(inner);
305    self.next();
306  }
307
308  /// Sends the next request, once greetd answered the last.
309  fn next(self: &Rc<Self>) {
310    {
311      let inner = self.inner.borrow();
312      if inner.asked.is_some() || inner.queue.is_empty() {
313        return;
314      }
315    }
316    if self.inner.borrow().connection.is_none() && !self.connect() {
317      return;
318    }
319    let mut inner = self.inner.borrow_mut();
320    let Some((kind, mut bytes)) = inner.queue.pop_front() else {
321      return;
322    };
323    inner.asked = Some(kind);
324    let connection = inner
325      .connection
326      .as_mut()
327      .expect("invariant: connected above");
328    if connection.outbox.is_empty() {
329      connection.outbox = bytes;
330      connection.sent = 0;
331    } else {
332      connection.outbox.extend_from_slice(&bytes);
333      zero(&mut bytes);
334    }
335    drop(inner);
336    self.flush();
337  }
338
339  /// Connects to greetd. `false`, with the state failed and the queue
340  /// dropped, if it isn't there.
341  fn connect(self: &Rc<Self>) -> bool {
342    let Some(event_loop) = self.shared.event_loop() else {
343      return false;
344    };
345    let path = self.shared.config.greetd.clone();
346    let opened = path
347      .as_ref()
348      .ok_or_else(|| std::io::Error::other("GREETD_SOCK isn't set"))
349      .and_then(|path| {
350        let stream = UnixStream::connect(path)?;
351        stream.set_nonblocking(true)?;
352        let fd = stream.as_fd().try_clone_to_owned()?;
353        Ok((stream, fd))
354      });
355    let (stream, fd) = match opened {
356      Ok(opened) => opened,
357      Err(error) => {
358        let path = path.as_ref().map(|path| path.display().to_string());
359        tracing::warn!(?path, %error, "can't reach greetd");
360        self.fail(format!("can't reach greetd: {error}"), false);
361        return false;
362      }
363    };
364    let service = Rc::clone(self);
365    let Some(reader) = event_loop.insert_fd(fd, Interest::READ, Box::new(move || service.read()))
366    else {
367      self.fail(String::from("can't follow greetd's socket"), false);
368      return false;
369    };
370    self.inner.borrow_mut().connection = Some(Connection {
371      stream,
372      reader,
373      writer: None,
374      inbox: Vec::new(),
375      outbox: Vec::new(),
376      sent: 0,
377    });
378    true
379  }
380
381  /// Writes what the socket takes, zeroing what went, and waits for room
382  /// for the rest.
383  fn flush(self: &Rc<Self>) {
384    let Some(event_loop) = self.shared.event_loop() else {
385      return;
386    };
387    let mut inner = self.inner.borrow_mut();
388    let Some(connection) = inner.connection.as_mut() else {
389      return;
390    };
391    let mut broken = None;
392    while connection.sent < connection.outbox.len() {
393      match connection
394        .stream
395        .write(&connection.outbox[connection.sent..])
396      {
397        Ok(written) => connection.sent += written,
398        Err(error) if error.kind() == ErrorKind::Interrupted => {}
399        Err(error) if error.kind() == ErrorKind::WouldBlock => break,
400        Err(error) => {
401          broken = Some(error.to_string());
402          break;
403        }
404      }
405    }
406    if connection.sent == connection.outbox.len() {
407      zero(&mut connection.outbox);
408      connection.outbox.clear();
409      connection.sent = 0;
410    }
411    if broken.is_none() {
412      if connection.outbox.is_empty() {
413        if let Some(writer) = connection.writer.take() {
414          event_loop.remove(writer);
415        }
416      } else if connection.writer.is_none() {
417        match connection.stream.as_fd().try_clone_to_owned() {
418          Ok(fd) => {
419            let service = Rc::clone(self);
420            let write = Box::new(move || service.flush());
421            connection.writer = event_loop.insert_fd(fd, Interest::WRITE, write);
422          }
423          Err(error) => broken = Some(error.to_string()),
424        }
425      }
426    }
427    drop(inner);
428    if let Some(why) = broken {
429      self.lost(&why);
430    }
431  }
432
433  /// Reads what greetd sent and acts on each whole message.
434  fn read(self: &Rc<Self>) {
435    let mut chunk = [0; 16 * 1024];
436    let mut inner = self.inner.borrow_mut();
437    let Some(connection) = inner.connection.as_mut() else {
438      return;
439    };
440    let why = match connection.stream.read(&mut chunk) {
441      Ok(0) => Some(String::from("greetd closed the socket")),
442      Ok(read) => {
443        connection.inbox.extend_from_slice(&chunk[..read]);
444        None
445      }
446      Err(error) if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::Interrupted) => {
447        return;
448      }
449      Err(error) => Some(error.to_string()),
450    };
451    if let Some(why) = why {
452      drop(inner);
453      return self.lost(&why);
454    }
455    let mut replies = Vec::new();
456    let mut broken = None;
457    while connection.inbox.len() >= 4 {
458      let length = u32::from_ne_bytes(connection.inbox[..4].try_into().expect("four bytes"));
459      let length = length as usize;
460      if length > MAX_MESSAGE {
461        broken = Some(format!("greetd sent a message of {length} bytes"));
462        break;
463      }
464      if connection.inbox.len() < 4 + length {
465        break;
466      }
467      let body: Vec<u8> = connection.inbox.drain(..4 + length).skip(4).collect();
468      replies.push(body);
469    }
470    drop(inner);
471    if let Some(why) = broken {
472      return self.lost(&why);
473    }
474    for body in replies {
475      match serde_json::from_slice::<Reply>(&body) {
476        Ok(reply) => self.answered(reply),
477        Err(error) => {
478          tracing::warn!(%error, "a message greetd sent doesn't parse");
479          return self.lost("greetd sent something it shouldn't");
480        }
481      }
482    }
483  }
484
485  /// Acts on greetd's answer to the request it was answering.
486  fn answered(self: &Rc<Self>, reply: Reply) {
487    let (state, messages) = self.signals();
488    let (asked, superseded) = {
489      let mut inner = self.inner.borrow_mut();
490      let cancel = |(kind, _): &(Kind, Vec<u8>)| matches!(kind, Kind::Cancel { .. });
491      (inner.asked.take(), inner.queue.iter().any(cancel))
492    };
493    let Some(kind) = asked else {
494      tracing::warn!("greetd answered nothing asked");
495      return;
496    };
497    if superseded && !matches!(kind, Kind::Cancel { .. }) {
498      // A queued cancel ends this attempt, so its answers show nothing.
499      return self.next();
500    }
501    match (kind, reply) {
502      // A cancel's error means greetd had no session left to end.
503      (Kind::Cancel { asked }, _) => {
504        if asked {
505          set(state, GreetState::Idle);
506        }
507      }
508      (Kind::Create | Kind::Respond, Reply::Success) => set(state, GreetState::Authenticated),
509      (Kind::Start, Reply::Success) => set(state, GreetState::Started),
510      (
511        kind,
512        Reply::Error {
513          error_type,
514          description,
515        },
516      ) => {
517        let auth = error_type == "auth_error";
518        set(
519          state,
520          GreetState::Failed {
521            message: cut(description),
522            auth,
523          },
524        );
525        // A failed attempt is ended, so another can begin.
526        if matches!(kind, Kind::Create | Kind::Respond) {
527          let cancel = json!({"type": "cancel_session"});
528          let bytes = frame(cancel.to_string().as_bytes());
529          self.queue(Kind::Cancel { asked: false }, bytes, true);
530        }
531      }
532      (
533        _,
534        Reply::AuthMessage {
535          auth_message_type,
536          auth_message,
537        },
538      ) => match auth_message_type.as_str() {
539        kind @ ("visible" | "secret") => set(
540          state,
541          GreetState::Asking {
542            message: cut(auth_message),
543            visible: kind == "visible",
544          },
545        ),
546        kind => {
547          let message = GreetMessage {
548            text: cut(auth_message),
549            error: kind == "error",
550          };
551          messages.try_update(|all| {
552            if all.len() == MAX_MESSAGES {
553              all.remove(0);
554            }
555            all.push(message);
556          });
557          // Informative: acknowledged with no answer, before anything else.
558          self.queue(Kind::Respond, answer_frame(None), true);
559        }
560      },
561    }
562    self.next();
563  }
564
565  /// The request in flight failed for `message`.
566  fn fail(&self, message: String, auth: bool) {
567    let (state, _) = self.signals();
568    set(state, GreetState::Failed { message, auth });
569    let mut inner = self.inner.borrow_mut();
570    for (_, bytes) in &mut inner.queue {
571      zero(bytes);
572    }
573    inner.queue.clear();
574    inner.asked = None;
575    inner.session = false;
576  }
577
578  /// Drops the connection, which ends the session being made.
579  fn lost(&self, why: &str) {
580    tracing::warn!(why, "lost the connection to greetd");
581    let connection = self.inner.borrow_mut().connection.take();
582    if let (Some(mut connection), Some(event_loop)) = (connection, self.shared.event_loop()) {
583      event_loop.remove(connection.reader);
584      if let Some(writer) = connection.writer {
585        event_loop.remove(writer);
586      }
587      zero(&mut connection.outbox);
588    }
589    self.fail(format!("lost greetd: {why}"), false);
590  }
591}
592
593/// `body` with its length before it, as greetd reads it.
594fn frame(body: &[u8]) -> Vec<u8> {
595  let length = u32::try_from(body.len()).unwrap_or(u32::MAX);
596  let mut framed = Vec::with_capacity(4 + body.len());
597  framed.extend_from_slice(&length.to_ne_bytes());
598  framed.extend_from_slice(body);
599  framed
600}
601
602/// An answer framed for greetd, written straight into one buffer sized so
603/// it never grows: no copy of the answer is left unzeroed.
604fn answer_frame(answer: Option<&str>) -> Vec<u8> {
605  #[derive(Serialize)]
606  struct Answer<'a> {
607    #[serde(rename = "type")]
608    kind: &'static str,
609    response: Option<&'a str>,
610  }
611  // JSON writes a byte as at most six.
612  let mut bytes = Vec::with_capacity(64 + 6 * answer.map_or(0, str::len));
613  bytes.extend_from_slice(&[0; 4]);
614  let answer = Answer {
615    kind: "post_auth_message_response",
616    response: answer,
617  };
618  serde_json::to_writer(&mut bytes, &answer).expect("invariant: a string serializes");
619  let length = u32::try_from(bytes.len() - 4).unwrap_or(u32::MAX);
620  bytes[..4].copy_from_slice(&length.to_ne_bytes());
621  bytes
622}
623
624/// Overwrites `bytes` with zeros, as for an answer once sent.
625fn zero(bytes: &mut [u8]) {
626  for byte in bytes.iter_mut() {
627    *byte = 0;
628  }
629  std::hint::black_box(bytes);
630}
631
632/// `text` cut to [`MAX_TEXT`] bytes at a character.
633fn cut(mut text: String) -> String {
634  if text.len() > MAX_TEXT {
635    text.truncate(text.floor_char_boundary(MAX_TEXT));
636  }
637  text
638}
639
640/// Sets `signal` unless it holds `value` or is gone.
641fn set<T: PartialEq + 'static>(signal: Signal<T>, value: T) {
642  if untrack(|| signal.try_with(|old| *old != value)) == Some(true) {
643    signal.try_set(value);
644  }
645}