Rust · 6018 bytes Raw Blame History
1 //! IPC protocol for gardm display manager
2 //!
3 //! Defines the JSON-based protocol between gardmd (daemon) and gardm-greeter (UI).
4
5 use serde::{Deserialize, Serialize};
6 use std::path::PathBuf;
7 use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
8 use tokio::net::UnixStream;
9
10 /// Socket path for gardm IPC
11 pub const SOCKET_PATH: &str = "/run/gardm.sock";
12
13 /// Default session type for backward compatibility
14 fn default_session_type() -> String {
15 "x11".to_string()
16 }
17
18 /// Requests from greeter to daemon
19 #[derive(Debug, Clone, Serialize, Deserialize)]
20 #[serde(tag = "type", rename_all = "snake_case")]
21 pub enum Request {
22 /// Create a new authentication session for a user
23 CreateSession { username: String },
24
25 /// Provide authentication response (password, OTP, etc.)
26 Authenticate { response: String },
27
28 /// Start the user's session after successful auth
29 StartSession {
30 /// Session command (e.g., ["gar-session.sh"])
31 cmd: Vec<String>,
32 /// Session type: "x11" or "wayland"
33 #[serde(default = "default_session_type")]
34 session_type: String,
35 /// Additional environment variables
36 #[serde(default)]
37 env: Vec<String>,
38 },
39
40 /// Cancel the current authentication attempt
41 CancelSession,
42
43 /// Request system shutdown
44 Shutdown,
45
46 /// Request system reboot
47 Reboot,
48
49 /// Request system suspend
50 Suspend,
51
52 /// Get list of available sessions
53 ListSessions,
54
55 /// Get list of available users
56 ListUsers,
57 }
58
59 /// Responses from daemon to greeter
60 #[derive(Debug, Clone, Serialize, Deserialize)]
61 #[serde(tag = "type", rename_all = "snake_case")]
62 pub enum Response {
63 /// Operation completed successfully
64 Success,
65
66 /// PAM is requesting user input
67 AuthPrompt {
68 /// Prompt message (e.g., "Password:")
69 prompt: String,
70 /// Whether to echo input (false for passwords)
71 echo: bool,
72 },
73
74 /// PAM informational message
75 AuthInfo { message: String },
76
77 /// Authentication failed
78 AuthError { message: String },
79
80 /// General error
81 Error { message: String },
82
83 /// List of available sessions
84 Sessions {
85 sessions: Vec<SessionInfo>,
86 /// Default session ID from daemon config
87 #[serde(default)]
88 default_session: Option<String>,
89 },
90
91 /// List of available users
92 Users { users: Vec<UserInfo> },
93 }
94
95 /// Information about an available session
96 #[derive(Debug, Clone, Serialize, Deserialize)]
97 pub struct SessionInfo {
98 /// Session identifier (desktop file name without .desktop)
99 pub id: String,
100 /// Display name
101 pub name: String,
102 /// Optional comment/description
103 pub comment: Option<String>,
104 /// Exec command
105 pub exec: String,
106 /// Session type (x11, wayland)
107 pub session_type: String,
108 }
109
110 /// Information about a user
111 #[derive(Debug, Clone, Serialize, Deserialize)]
112 pub struct UserInfo {
113 /// Username
114 pub name: String,
115 /// Full name (GECOS field)
116 pub full_name: Option<String>,
117 /// Home directory
118 pub home: PathBuf,
119 /// Path to user avatar (if available)
120 pub avatar: Option<PathBuf>,
121 }
122
123 /// IPC client for connecting to gardmd
124 pub struct Client {
125 reader: BufReader<tokio::net::unix::OwnedReadHalf>,
126 writer: tokio::net::unix::OwnedWriteHalf,
127 }
128
129 impl Client {
130 /// Connect to the daemon
131 pub async fn connect() -> Result<Self, std::io::Error> {
132 Self::connect_to(SOCKET_PATH).await
133 }
134
135 /// Connect to a specific socket path
136 pub async fn connect_to(path: &str) -> Result<Self, std::io::Error> {
137 let stream = UnixStream::connect(path).await?;
138 let (read, write) = stream.into_split();
139 Ok(Self {
140 reader: BufReader::new(read),
141 writer: write,
142 })
143 }
144
145 /// Send a request to the daemon
146 pub async fn send(&mut self, request: &Request) -> Result<(), std::io::Error> {
147 let json = serde_json::to_string(request).map_err(|e| {
148 std::io::Error::new(std::io::ErrorKind::InvalidData, e)
149 })?;
150 self.writer.write_all(json.as_bytes()).await?;
151 self.writer.write_all(b"\n").await?;
152 self.writer.flush().await?;
153 Ok(())
154 }
155
156 /// Receive a response from the daemon
157 pub async fn recv(&mut self) -> Result<Response, std::io::Error> {
158 let mut line = String::new();
159 let n = self.reader.read_line(&mut line).await?;
160 if n == 0 {
161 return Err(std::io::Error::new(
162 std::io::ErrorKind::UnexpectedEof,
163 "daemon closed connection",
164 ));
165 }
166 serde_json::from_str(&line).map_err(|e| {
167 std::io::Error::new(std::io::ErrorKind::InvalidData, e)
168 })
169 }
170
171 /// Send request and wait for response
172 pub async fn request(&mut self, request: &Request) -> Result<Response, std::io::Error> {
173 self.send(request).await?;
174 self.recv().await
175 }
176 }
177
178 #[cfg(test)]
179 mod tests {
180 use super::*;
181
182 #[test]
183 fn test_request_serialization() {
184 let req = Request::CreateSession {
185 username: "testuser".to_string(),
186 };
187 let json = serde_json::to_string(&req).unwrap();
188 assert!(json.contains("create_session"));
189 assert!(json.contains("testuser"));
190 }
191
192 #[test]
193 fn test_response_serialization() {
194 let resp = Response::AuthPrompt {
195 prompt: "Password:".to_string(),
196 echo: false,
197 };
198 let json = serde_json::to_string(&resp).unwrap();
199 assert!(json.contains("auth_prompt"));
200 assert!(json.contains("Password:"));
201 }
202
203 #[test]
204 fn test_request_deserialization() {
205 let json = r#"{"type":"authenticate","response":"secret123"}"#;
206 let req: Request = serde_json::from_str(json).unwrap();
207 match req {
208 Request::Authenticate { response } => assert_eq!(response, "secret123"),
209 _ => panic!("Wrong variant"),
210 }
211 }
212 }
213