1use std::io::{BufRead, BufReader, ErrorKind, Write};
5use std::os::unix::net::{UnixListener, UnixStream};
6use std::path::Path;
7use std::sync::Arc;
8use std::time::{Duration, Instant};
9
10use crate::limits::{Counters, Held, Limits, Registry, Slot, head_of};
11use crate::protocol::{Request, Response};
12use crate::service::Service;
13
14pub const MAX_SOCKET_PATH: usize = 100;
16
17#[derive(Clone, Debug)]
20pub struct Access {
21 pub mode: u32,
22 pub group: Option<u32>,
23 pub allowed_uids: Option<Vec<u32>>,
26}
27
28impl Default for Access {
29 fn default() -> Self {
30 Self {
31 mode: 0o600,
32 group: None,
33 allowed_uids: None,
34 }
35 }
36}
37
38pub fn bind(path: &Path) -> std::io::Result<UnixListener> {
39 bind_with(path, &Access::default())
40}
41
42pub fn bind_with(path: &Path, access: &Access) -> std::io::Result<UnixListener> {
43 if path.as_os_str().len() > MAX_SOCKET_PATH {
44 return Err(std::io::Error::new(
45 std::io::ErrorKind::InvalidInput,
46 format!(
47 "socket path {} is longer than {MAX_SOCKET_PATH} bytes; set LONCA_SOCKET to a shorter path",
48 path.display()
49 ),
50 ));
51 }
52 if path.exists() {
53 if UnixStream::connect(path).is_ok() {
55 return Err(std::io::Error::new(
56 std::io::ErrorKind::AddrInUse,
57 "a Lonca daemon is already running",
58 ));
59 }
60 std::fs::remove_file(path)?;
61 }
62 let l = UnixListener::bind(path)?;
63 use std::os::unix::fs::PermissionsExt;
64 std::fs::set_permissions(path, std::fs::Permissions::from_mode(access.mode))?;
65 if let Some(gid) = access.group {
66 std::os::unix::fs::chown(path, None, Some(gid))?;
67 }
68 Ok(l)
69}
70
71#[cfg(not(target_os = "linux"))]
72pub use crate::peer_other::peer_in_sandbox;
73
74pub use crate::sys::peer_uid;
75
76#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
79fn pid_namespaces(status: &str) -> usize {
80 let line = status.lines().find_map(|l| l.strip_prefix("NSpid:"));
81 line.map_or(0, |l| l.split_whitespace().count())
82}
83
84#[cfg(target_os = "linux")]
89pub fn peer_in_sandbox(s: &UnixStream) -> bool {
90 let status = |who: String| std::fs::read_to_string(format!("/proc/{who}/status")).ok();
91 let Some(cred) = crate::sys::peer_cred(s) else {
92 return false;
93 };
94 match (status(cred.pid.to_string()), status("self".into())) {
95 (Some(peer), Some(me)) => pid_namespaces(&peer) > pid_namespaces(&me),
96 _ => false,
97 }
98}
99
100pub fn group_id(name: &str) -> Option<u32> {
102 if let Ok(n) = name.parse() {
103 return Some(n);
104 }
105 crate::sys::group_named(name)
106}
107
108pub fn serve(listener: UnixListener, svc: Arc<Service>) {
109 serve_with(listener, svc, Access::default(), Limits::default())
110}
111
112const ACCEPT_PAUSE: Duration = Duration::from_millis(20);
115
116pub fn serve_with(listener: UnixListener, svc: Arc<Service>, access: Access, limits: Limits) {
119 let registry = Registry::new(limits);
120 for conn in listener.incoming() {
121 let Ok(stream) = conn else {
122 std::thread::sleep(ACCEPT_PAUSE);
123 continue;
124 };
125 let uid = peer_uid(&stream);
126 if let Some(allowed) = &access.allowed_uids
127 && !uid.is_some_and(|u| allowed.contains(&u))
128 {
129 use crate::security::{Event, kind};
130 let e = Event::new(svc.now(), kind::REFUSED_PEER)
131 .uid(uid)
132 .detail("a uid the socket does not serve");
133 svc.security_event(e);
134 continue; }
136 let stream = Arc::new(stream);
137 let Some((slot, evicted)) = registry.admit(uid.unwrap_or(u32::MAX), stream.clone()) else {
139 Counters::add(&svc.counters.busy);
140 let full = "the daemon has no room for another connection";
141 let _ = write_by(&stream, &Response::err(0, "busy", full), ACCEPT_WRITE);
142 continue;
143 };
144 if evicted {
145 Counters::add(&svc.counters.evicted);
146 }
147 let svc = svc.clone();
148 let _ = std::thread::Builder::new().spawn(move || {
149 let _ = handle_conn(&stream, &slot, &svc);
150 });
151 }
152}
153
154const ACCEPT_WRITE: Duration = Duration::from_millis(100);
157
158enum Incoming {
160 Line(String),
161 Refused(Response),
163 End,
164 TimedOut {
166 started: bool,
167 },
168}
169
170fn after_failed_read(e: std::io::Error, started: bool) -> std::io::Result<Incoming> {
172 match e.kind() {
173 ErrorKind::WouldBlock | ErrorKind::TimedOut => Ok(Incoming::TimedOut { started }),
174 _ => Err(e),
175 }
176}
177
178fn read_request(
181 r: &mut BufReader<&UnixStream>,
182 held: &mut Held,
183 limits: &Limits,
184 svc: &Service,
185) -> std::io::Result<Incoming> {
186 let stream = *r.get_ref();
187 let mut buf = Vec::new();
188 let mut id = 0;
189 let mut long = false;
190 let mut refused = None;
191 let mut deadline = None;
192 loop {
193 let wait = deadline.map_or(limits.idle, |d: Instant| {
194 d.saturating_duration_since(Instant::now())
195 });
196 if wait.is_zero() {
197 return Ok(Incoming::TimedOut { started: true });
198 }
199 let _ = stream.set_read_timeout(Some(wait));
202 let chunk = match r.fill_buf() {
203 Ok(chunk) => chunk,
204 Err(e) => return after_failed_read(e, deadline.is_some()),
205 };
206 if chunk.is_empty() {
207 return Ok(match refused {
208 Some(why) => Incoming::Refused(why),
209 None if buf.is_empty() => Incoming::End,
210 None => Incoming::Line(String::from_utf8_lossy(&buf).into_owned()),
211 });
212 }
213 deadline.get_or_insert_with(|| Instant::now() + limits.line);
214 let newline = chunk.iter().position(|&b| b == b'\n');
215 let n = newline.unwrap_or(chunk.len());
216 if refused.is_none() {
217 buf.extend_from_slice(&chunk[..n]);
218 }
219 r.consume(n + usize::from(newline.is_some()));
220 if refused.is_none() && buf.len() > limits.short_line {
221 if !long {
222 let head = head_of(&buf[..limits.short_line]);
223 id = head.id;
224 match svc.long_line(&head) {
225 Ok(()) => long = true,
226 Err((code, msg)) => refused = Some(Response::err(id, &code, msg)),
227 }
228 }
229 if long && buf.len() > limits.long_line {
230 let msg = format!("a request line holds at most {} bytes", limits.long_line);
231 refused = Some(Response::err(id, "too_large", msg));
232 } else if long && !held.grow(buf.len()) {
233 let msg = "too many long requests at once; send it again";
234 refused = Some(Response::err(id, "busy", msg));
235 }
236 if let Some(why) = &refused {
237 Counters::add(match why.error.as_ref().map(|e| e.code.as_str()) {
238 Some("busy") => &svc.counters.busy,
239 _ => &svc.counters.too_large,
240 });
241 buf = Vec::new();
242 }
243 }
244 if newline.is_some() {
245 return Ok(match refused {
246 Some(why) => Incoming::Refused(why),
247 None => Incoming::Line(String::from_utf8_lossy(&buf).into_owned()),
248 });
249 }
250 }
251}
252
253fn write_by(mut s: &UnixStream, v: &Response, limit: Duration) -> std::io::Result<()> {
256 let mut bytes = serde_json::to_vec(v)?;
257 bytes.push(b'\n');
258 let deadline = Instant::now() + limit;
259 let mut left = &bytes[..];
260 while !left.is_empty() {
261 let wait = deadline.saturating_duration_since(Instant::now());
262 if wait.is_zero() {
263 return Err(ErrorKind::TimedOut.into());
264 }
265 let _ = s.set_write_timeout(Some(wait)); left = &left[s.write(left)?..];
267 }
268 Ok(())
269}
270
271fn farewell(stream: &UnixStream, limits: &Limits, code: &str, msg: &str) {
274 let _ = write_by(stream, &Response::err(0, code, msg), limits.write);
275}
276
277const NOT_READ: &str = "connection closed while quiet; this request was not run, send it again";
278
279fn handle_conn(stream: &UnixStream, slot: &Slot, svc: &Service) -> std::io::Result<()> {
280 let limits = slot.limits();
281 let mut reader = BufReader::new(stream);
282 loop {
283 let mut held = slot.hold();
284 let resp = match read_request(&mut reader, &mut held, limits, svc)? {
285 Incoming::End if slot.evicted() => {
286 farewell(stream, limits, "closed", NOT_READ);
287 return Ok(());
288 }
289 Incoming::End => return Ok(()),
290 Incoming::TimedOut { started: false } => {
291 Counters::add(&svc.counters.idle);
292 farewell(stream, limits, "closed", NOT_READ);
293 return Ok(());
294 }
295 Incoming::TimedOut { started: true } => {
296 Counters::add(&svc.counters.slow);
297 farewell(stream, limits, "timeout", "the request line took too long");
298 return Ok(());
299 }
300 Incoming::Refused(why) => {
301 use crate::security::{Event, kind};
302 let code = why
303 .error
304 .as_ref()
305 .map(|e| e.code.clone())
306 .unwrap_or_default();
307 if code == "too_large" {
308 let e = Event::new(svc.now(), kind::OVERSIZED)
309 .uid(slot.uid())
310 .code(&code)
311 .detail("a request line past its method's limit");
312 svc.security_event(e);
313 }
314 why
315 }
316 Incoming::Line(line) if line.trim().is_empty() => continue,
317 Incoming::Line(_) if !slot.begin() => {
318 farewell(stream, limits, "closed", NOT_READ);
319 return Ok(());
320 }
321 Incoming::Line(line) => match serde_json::from_str::<Request>(&line) {
322 Ok(req) => svc.handle_from(req, &|| peer_in_sandbox(stream)),
323 Err(e) => {
324 use crate::security::{Event, kind};
325 let ev = Event::new(svc.now(), kind::MALFORMED)
326 .uid(slot.uid())
327 .code("invalid")
328 .detail(&format!("not a request: {e}"));
329 svc.security_event(ev);
330 Response::err(0, "invalid", format!("bad request: {e}"))
331 }
332 },
333 };
334 if let Err(e) = write_by(stream, &resp, limits.write) {
335 Counters::add(&svc.counters.slow);
336 return Err(e);
337 }
338 slot.end();
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn a_response_nobody_reads_runs_out_of_time() {
348 let (a, _b) = UnixStream::pair().unwrap();
349 let big = Response::err(0, "x", "y".repeat(16 * 1024 * 1024));
350 let started = Instant::now();
351 let e = write_by(&a, &big, Duration::from_millis(100)).unwrap_err();
352 assert!(matches!(
353 e.kind(),
354 ErrorKind::WouldBlock | ErrorKind::TimedOut
355 ));
356 assert!(started.elapsed() < Duration::from_secs(5));
357 }
358
359 #[test]
360 fn only_a_deadline_is_a_timeout() {
361 for (kind, started) in [(ErrorKind::WouldBlock, true), (ErrorKind::TimedOut, false)] {
362 let after = after_failed_read(kind.into(), started);
363 assert!(matches!(after, Ok(Incoming::TimedOut { started: s }) if s == started));
364 }
365 let gone = after_failed_read(ErrorKind::ConnectionReset.into(), true);
366 assert_eq!(
367 gone.err().map(|e| e.kind()),
368 Some(ErrorKind::ConnectionReset)
369 );
370 }
371
372 #[test]
373 fn a_response_is_a_line() {
374 use std::io::Read;
375 let (a, mut b) = UnixStream::pair().unwrap();
376 write_by(&a, &Response::err(3, "x", "y"), Duration::from_secs(5)).unwrap();
377 drop(a);
378 let mut text = String::new();
379 b.read_to_string(&mut text).unwrap();
380 let line = text.strip_suffix('\n').unwrap();
381 assert_eq!(serde_json::from_str::<Response>(line).unwrap().id, 3);
382 }
383
384 #[test]
385 fn a_test_process_is_in_no_sandbox() {
386 let (a, _b) = UnixStream::pair().unwrap();
387 assert!(!peer_in_sandbox(&a));
388 }
389
390 #[test]
391 fn namespaces_are_counted_from_the_status() {
392 assert_eq!(pid_namespaces("Name:\tx\nNSpid:\t1234\nNSsid:\t1\n"), 1);
393 assert_eq!(pid_namespaces("NSpid:\t1234\t2\n"), 2);
394 assert_eq!(pid_namespaces("Name:\tx\n"), 0);
395 }
396
397 #[test]
398 fn peer_uid_is_ours() {
399 let (a, _b) = UnixStream::pair().unwrap();
400 assert_eq!(peer_uid(&a), Some(crate::sys::current_uid()));
401 }
402
403 #[test]
404 fn groups_resolve() {
405 assert_eq!(group_id("0"), Some(0));
406 assert!(group_id("wheel").is_some() || group_id("root").is_some());
407 assert_eq!(group_id("no-such-group-lonca"), None);
408 }
409}