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>) {
127 if let Some(service) = &self.service {
128 service.start(command, env);
129 }
130 }
131
132 pub fn cancel(&self) {
134 if let Some(service) = &self.service {
135 service.send(
136 Kind::Cancel { asked: true },
137 json!({"type": "cancel_session"}),
138 );
139 }
140 }
141}
142
143pub(crate) fn greetd(cx: &Cx) -> Greetd {
145 let Some(service) = cx.service::<Service>() else {
146 static LOGGED: Once = Once::new();
147 LOGGED.call_once(|| {
148 tracing::warn!("cx.greetd() needs the services the shell provides; it never connects");
149 });
150 return Greetd {
151 service: None,
152 state: cx.signal(GreetState::Idle).read_only(),
153 messages: cx.signal(Vec::new()).read_only(),
154 };
155 };
156 let (state, messages) = service.signals();
157 Greetd {
158 service: Some(service),
159 state: state.read_only(),
160 messages: messages.read_only(),
161 }
162}
163
164pub(crate) struct Service {
166 shared: Rc<Shared>,
167 inner: RefCell<Inner>,
168}
169
170#[derive(Default)]
171struct Inner {
172 signals: Option<(Signal<GreetState>, Signal<Vec<GreetMessage>>)>,
173 connection: Option<Connection>,
174 queue: VecDeque<(Kind, Vec<u8>)>,
176 asked: Option<Kind>,
178 session: bool,
181}
182
183struct Connection {
184 stream: UnixStream,
185 reader: RegistrationToken,
186 writer: Option<RegistrationToken>,
187 inbox: Vec<u8>,
188 outbox: Vec<u8>,
190 sent: usize,
191}
192
193#[derive(Clone, Copy, Debug, PartialEq)]
195enum Kind {
196 Create,
197 Respond,
198 Start,
199 Cancel {
202 asked: bool,
203 },
204}
205
206#[derive(Deserialize)]
208#[serde(tag = "type", rename_all = "snake_case")]
209enum Reply {
210 Success,
211 Error {
212 error_type: String,
213 description: String,
214 },
215 AuthMessage {
216 auth_message_type: String,
217 auth_message: String,
218 },
219}
220
221impl Service {
222 pub(crate) fn new(shared: Rc<Shared>) -> Service {
223 Service {
224 shared,
225 inner: RefCell::default(),
226 }
227 }
228
229 fn signals(&self) -> (Signal<GreetState>, Signal<Vec<GreetMessage>>) {
231 let mut inner = self.inner.borrow_mut();
232 *inner.signals.get_or_insert_with(|| {
233 (
234 self.shared.root.signal(GreetState::Idle),
235 self.shared.root.signal(Vec::new()),
236 )
237 })
238 }
239
240 fn create_session(self: &Rc<Self>, user: &str) {
241 if self.inner.borrow().session {
242 self.send(
244 Kind::Cancel { asked: false },
245 json!({"type": "cancel_session"}),
246 );
247 }
248 let (_, messages) = self.signals();
249 set(messages, Vec::new());
250 self.send(
251 Kind::Create,
252 json!({"type": "create_session", "username": user}),
253 );
254 }
255
256 fn respond(self: &Rc<Self>, answer: &str) {
257 if !matches!(self.state(), GreetState::Asking { .. }) {
258 tracing::warn!("greetd asked nothing, so the answer is dropped");
259 return;
260 }
261 self.queue(Kind::Respond, answer_frame(Some(answer)), false);
262 }
263
264 fn start(self: &Rc<Self>, command: Vec<String>, env: Vec<String>) {
265 if self.state() != GreetState::Authenticated {
266 tracing::warn!("a session starts only once authenticated; nothing is sent");
267 return;
268 }
269 self.send(
270 Kind::Start,
271 json!({"type": "start_session", "cmd": command, "env": env}),
272 );
273 }
274
275 fn send(self: &Rc<Self>, kind: Kind, body: serde_json::Value) {
276 self.queue(kind, frame(body.to_string().as_bytes()), false);
277 }
278
279 fn state(&self) -> GreetState {
280 let (state, _) = self.signals();
281 untrack(|| state.try_with(Clone::clone)).unwrap_or_default()
282 }
283
284 fn queue(self: &Rc<Self>, kind: Kind, bytes: Vec<u8>, front: bool) {
287 let (state, _) = self.signals();
288 if kind != (Kind::Cancel { asked: false }) {
289 set(state, GreetState::Busy);
290 }
291 let mut inner = self.inner.borrow_mut();
292 match kind {
293 Kind::Create => inner.session = true,
294 Kind::Cancel { .. } => inner.session = false,
295 Kind::Respond | Kind::Start => {}
296 }
297 if front {
298 inner.queue.push_front((kind, bytes));
299 } else {
300 inner.queue.push_back((kind, bytes));
301 }
302 drop(inner);
303 self.next();
304 }
305
306 fn next(self: &Rc<Self>) {
308 {
309 let inner = self.inner.borrow();
310 if inner.asked.is_some() || inner.queue.is_empty() {
311 return;
312 }
313 }
314 if self.inner.borrow().connection.is_none() && !self.connect() {
315 return;
316 }
317 let mut inner = self.inner.borrow_mut();
318 let Some((kind, mut bytes)) = inner.queue.pop_front() else {
319 return;
320 };
321 inner.asked = Some(kind);
322 let connection = inner
323 .connection
324 .as_mut()
325 .expect("invariant: connected above");
326 if connection.outbox.is_empty() {
327 connection.outbox = bytes;
328 connection.sent = 0;
329 } else {
330 connection.outbox.extend_from_slice(&bytes);
331 zero(&mut bytes);
332 }
333 drop(inner);
334 self.flush();
335 }
336
337 fn connect(self: &Rc<Self>) -> bool {
340 let Some(event_loop) = self.shared.event_loop() else {
341 return false;
342 };
343 let path = self.shared.config.greetd.clone();
344 let opened = path
345 .as_ref()
346 .ok_or_else(|| std::io::Error::other("GREETD_SOCK isn't set"))
347 .and_then(|path| {
348 let stream = UnixStream::connect(path)?;
349 stream.set_nonblocking(true)?;
350 let fd = stream.as_fd().try_clone_to_owned()?;
351 Ok((stream, fd))
352 });
353 let (stream, fd) = match opened {
354 Ok(opened) => opened,
355 Err(error) => {
356 let path = path.as_ref().map(|path| path.display().to_string());
357 tracing::warn!(?path, %error, "can't reach greetd");
358 self.fail(format!("can't reach greetd: {error}"), false);
359 return false;
360 }
361 };
362 let service = Rc::clone(self);
363 let Some(reader) = event_loop.insert_fd(fd, Interest::READ, Box::new(move || service.read()))
364 else {
365 self.fail(String::from("can't follow greetd's socket"), false);
366 return false;
367 };
368 self.inner.borrow_mut().connection = Some(Connection {
369 stream,
370 reader,
371 writer: None,
372 inbox: Vec::new(),
373 outbox: Vec::new(),
374 sent: 0,
375 });
376 true
377 }
378
379 fn flush(self: &Rc<Self>) {
382 let Some(event_loop) = self.shared.event_loop() else {
383 return;
384 };
385 let mut inner = self.inner.borrow_mut();
386 let Some(connection) = inner.connection.as_mut() else {
387 return;
388 };
389 let mut broken = None;
390 while connection.sent < connection.outbox.len() {
391 match connection
392 .stream
393 .write(&connection.outbox[connection.sent..])
394 {
395 Ok(written) => connection.sent += written,
396 Err(error) if error.kind() == ErrorKind::Interrupted => {}
397 Err(error) if error.kind() == ErrorKind::WouldBlock => break,
398 Err(error) => {
399 broken = Some(error.to_string());
400 break;
401 }
402 }
403 }
404 if connection.sent == connection.outbox.len() {
405 zero(&mut connection.outbox);
406 connection.outbox.clear();
407 connection.sent = 0;
408 }
409 if broken.is_none() {
410 if connection.outbox.is_empty() {
411 if let Some(writer) = connection.writer.take() {
412 event_loop.remove(writer);
413 }
414 } else if connection.writer.is_none() {
415 match connection.stream.as_fd().try_clone_to_owned() {
416 Ok(fd) => {
417 let service = Rc::clone(self);
418 let write = Box::new(move || service.flush());
419 connection.writer = event_loop.insert_fd(fd, Interest::WRITE, write);
420 }
421 Err(error) => broken = Some(error.to_string()),
422 }
423 }
424 }
425 drop(inner);
426 if let Some(why) = broken {
427 self.lost(&why);
428 }
429 }
430
431 fn read(self: &Rc<Self>) {
433 let mut chunk = [0; 16 * 1024];
434 let mut inner = self.inner.borrow_mut();
435 let Some(connection) = inner.connection.as_mut() else {
436 return;
437 };
438 let why = match connection.stream.read(&mut chunk) {
439 Ok(0) => Some(String::from("greetd closed the socket")),
440 Ok(read) => {
441 connection.inbox.extend_from_slice(&chunk[..read]);
442 None
443 }
444 Err(error) if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::Interrupted) => {
445 return;
446 }
447 Err(error) => Some(error.to_string()),
448 };
449 if let Some(why) = why {
450 drop(inner);
451 return self.lost(&why);
452 }
453 let mut replies = Vec::new();
454 let mut broken = None;
455 while connection.inbox.len() >= 4 {
456 let length = u32::from_ne_bytes(connection.inbox[..4].try_into().expect("four bytes"));
457 let length = length as usize;
458 if length > MAX_MESSAGE {
459 broken = Some(format!("greetd sent a message of {length} bytes"));
460 break;
461 }
462 if connection.inbox.len() < 4 + length {
463 break;
464 }
465 let body: Vec<u8> = connection.inbox.drain(..4 + length).skip(4).collect();
466 replies.push(body);
467 }
468 drop(inner);
469 if let Some(why) = broken {
470 return self.lost(&why);
471 }
472 for body in replies {
473 match serde_json::from_slice::<Reply>(&body) {
474 Ok(reply) => self.answered(reply),
475 Err(error) => {
476 tracing::warn!(%error, "a message greetd sent doesn't parse");
477 return self.lost("greetd sent something it shouldn't");
478 }
479 }
480 }
481 }
482
483 fn answered(self: &Rc<Self>, reply: Reply) {
485 let (state, messages) = self.signals();
486 let (asked, superseded) = {
487 let mut inner = self.inner.borrow_mut();
488 let cancel = |(kind, _): &(Kind, Vec<u8>)| matches!(kind, Kind::Cancel { .. });
489 (inner.asked.take(), inner.queue.iter().any(cancel))
490 };
491 let Some(kind) = asked else {
492 tracing::warn!("greetd answered nothing asked");
493 return;
494 };
495 if superseded && !matches!(kind, Kind::Cancel { .. }) {
496 return self.next();
498 }
499 match (kind, reply) {
500 (Kind::Cancel { asked }, _) => {
502 if asked {
503 set(state, GreetState::Idle);
504 }
505 }
506 (Kind::Create | Kind::Respond, Reply::Success) => set(state, GreetState::Authenticated),
507 (Kind::Start, Reply::Success) => set(state, GreetState::Started),
508 (
509 kind,
510 Reply::Error {
511 error_type,
512 description,
513 },
514 ) => {
515 let auth = error_type == "auth_error";
516 set(
517 state,
518 GreetState::Failed {
519 message: cut(description),
520 auth,
521 },
522 );
523 if matches!(kind, Kind::Create | Kind::Respond) {
525 let cancel = json!({"type": "cancel_session"});
526 let bytes = frame(cancel.to_string().as_bytes());
527 self.queue(Kind::Cancel { asked: false }, bytes, true);
528 }
529 }
530 (
531 _,
532 Reply::AuthMessage {
533 auth_message_type,
534 auth_message,
535 },
536 ) => match auth_message_type.as_str() {
537 kind @ ("visible" | "secret") => set(
538 state,
539 GreetState::Asking {
540 message: cut(auth_message),
541 visible: kind == "visible",
542 },
543 ),
544 kind => {
545 let message = GreetMessage {
546 text: cut(auth_message),
547 error: kind == "error",
548 };
549 messages.try_update(|all| {
550 if all.len() == MAX_MESSAGES {
551 all.remove(0);
552 }
553 all.push(message);
554 });
555 self.queue(Kind::Respond, answer_frame(None), true);
557 }
558 },
559 }
560 self.next();
561 }
562
563 fn fail(&self, message: String, auth: bool) {
565 let (state, _) = self.signals();
566 set(state, GreetState::Failed { message, auth });
567 let mut inner = self.inner.borrow_mut();
568 for (_, bytes) in &mut inner.queue {
569 zero(bytes);
570 }
571 inner.queue.clear();
572 inner.asked = None;
573 inner.session = false;
574 }
575
576 fn lost(&self, why: &str) {
578 tracing::warn!(why, "lost the connection to greetd");
579 let connection = self.inner.borrow_mut().connection.take();
580 if let (Some(mut connection), Some(event_loop)) = (connection, self.shared.event_loop()) {
581 event_loop.remove(connection.reader);
582 if let Some(writer) = connection.writer {
583 event_loop.remove(writer);
584 }
585 zero(&mut connection.outbox);
586 }
587 self.fail(format!("lost greetd: {why}"), false);
588 }
589}
590
591fn frame(body: &[u8]) -> Vec<u8> {
593 let length = u32::try_from(body.len()).unwrap_or(u32::MAX);
594 let mut framed = Vec::with_capacity(4 + body.len());
595 framed.extend_from_slice(&length.to_ne_bytes());
596 framed.extend_from_slice(body);
597 framed
598}
599
600fn answer_frame(answer: Option<&str>) -> Vec<u8> {
603 #[derive(Serialize)]
604 struct Answer<'a> {
605 #[serde(rename = "type")]
606 kind: &'static str,
607 response: Option<&'a str>,
608 }
609 let mut bytes = Vec::with_capacity(64 + 6 * answer.map_or(0, str::len));
611 bytes.extend_from_slice(&[0; 4]);
612 let answer = Answer {
613 kind: "post_auth_message_response",
614 response: answer,
615 };
616 serde_json::to_writer(&mut bytes, &answer).expect("invariant: a string serializes");
617 let length = u32::try_from(bytes.len() - 4).unwrap_or(u32::MAX);
618 bytes[..4].copy_from_slice(&length.to_ne_bytes());
619 bytes
620}
621
622fn zero(bytes: &mut [u8]) {
624 for byte in bytes.iter_mut() {
625 *byte = 0;
626 }
627 std::hint::black_box(bytes);
628}
629
630fn cut(mut text: String) -> String {
632 if text.len() > MAX_TEXT {
633 text.truncate(text.floor_char_boundary(MAX_TEXT));
634 }
635 text
636}
637
638fn set<T: PartialEq + 'static>(signal: Signal<T>, value: T) {
640 if untrack(|| signal.try_with(|old| *old != value)) == Some(true) {
641 signal.try_set(value);
642 }
643}