1#[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
26pub(crate) const MAX_MESSAGE: usize = 64 * 1024;
29
30const MAX_TEXT: usize = 4 * 1024;
32
33pub(crate) const MAX_MESSAGES: usize = 64;
35
36#[derive(Clone, Debug, Default, PartialEq)]
38pub enum GreetState {
39 #[default]
41 Idle,
42 Busy,
44 Asking {
47 message: String,
49 visible: bool,
51 },
52 Authenticated,
54 Started,
56 Failed {
59 message: String,
61 auth: bool,
63 },
64}
65
66#[derive(Clone, Debug, PartialEq)]
68pub struct GreetMessage {
69 pub text: String,
71 pub error: bool,
73}
74
75#[derive(Clone)]
83pub struct Greetd {
84 service: Option<Rc<Service>>,
86 state: ReadSignal<GreetState>,
87 messages: ReadSignal<Vec<GreetMessage>>,
88}
89
90impl Greetd {
91 pub fn state(&self) -> ReadSignal<GreetState> {
93 self.state
94 }
95
96 pub fn messages(&self) -> ReadSignal<Vec<GreetMessage>> {
100 self.messages
101 }
102
103 pub fn create_session(&self, user: &str) {
106 if let Some(service) = &self.service {
107 service.create_session(user);
108 }
109 }
110
111 pub fn respond(&self, answer: &str) {
116 if let Some(service) = &self.service {
117 service.respond(answer);
118 }
119 }
120
121 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 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
145pub(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
166pub(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 queue: VecDeque<(Kind, Vec<u8>)>,
178 asked: Option<Kind>,
180 session: bool,
183}
184
185struct Connection {
186 stream: UnixStream,
187 reader: RegistrationToken,
188 writer: Option<RegistrationToken>,
189 inbox: Vec<u8>,
190 outbox: Vec<u8>,
192 sent: usize,
193}
194
195#[derive(Clone, Copy, Debug, PartialEq)]
197enum Kind {
198 Create,
199 Respond,
200 Start,
201 Cancel {
204 asked: bool,
205 },
206}
207
208#[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 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 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 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 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 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 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 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 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 return self.next();
500 }
501 match (kind, reply) {
502 (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 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 self.queue(Kind::Respond, answer_frame(None), true);
559 }
560 },
561 }
562 self.next();
563 }
564
565 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 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
593fn 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
602fn 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 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
624fn zero(bytes: &mut [u8]) {
626 for byte in bytes.iter_mut() {
627 *byte = 0;
628 }
629 std::hint::black_box(bytes);
630}
631
632fn 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
640fn 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}