1 | use async_trait::async_trait; |
2 | use futures_util::{future::poll_fn, StreamExt}; |
3 | #[cfg (unix)] |
4 | use nix::unistd::Uid; |
5 | use std::{ |
6 | collections::VecDeque, |
7 | convert::{TryFrom, TryInto}, |
8 | fmt::{self, Debug}, |
9 | path::PathBuf, |
10 | str::FromStr, |
11 | }; |
12 | use tracing::{instrument, trace}; |
13 | use zvariant::Str; |
14 | |
15 | use sha1::{Digest, Sha1}; |
16 | |
17 | use xdg_home::home_dir; |
18 | |
19 | #[cfg (windows)] |
20 | use crate::win32; |
21 | use crate::{ |
22 | file::FileLines, |
23 | guid::Guid, |
24 | raw::{Connection, Socket}, |
25 | Error, Result, |
26 | }; |
27 | |
28 | /// Authentication mechanisms |
29 | /// |
30 | /// See <https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms> |
31 | #[derive (Clone, Copy, Debug, PartialEq, Eq)] |
32 | pub enum AuthMechanism { |
33 | /// This is the recommended authentication mechanism on platforms where credentials can be |
34 | /// transferred out-of-band, in particular Unix platforms that can perform credentials-passing |
35 | /// over the `unix:` transport. |
36 | External, |
37 | |
38 | /// This mechanism is designed to establish that a client has the ability to read a private |
39 | /// file owned by the user being authenticated. |
40 | Cookie, |
41 | |
42 | /// Does not perform any authentication at all, and should not be accepted by message buses. |
43 | /// However, it might sometimes be useful for non-message-bus uses of D-Bus. |
44 | Anonymous, |
45 | } |
46 | |
47 | /// The result of a finalized handshake |
48 | /// |
49 | /// The result of a finalized [`ClientHandshake`] or [`ServerHandshake`]. It can be passed to |
50 | /// [`Connection::new_authenticated`] to initialize a connection. |
51 | /// |
52 | /// [`ClientHandshake`]: struct.ClientHandshake.html |
53 | /// [`ServerHandshake`]: struct.ServerHandshake.html |
54 | /// [`Connection::new_authenticated`]: ../struct.Connection.html#method.new_authenticated |
55 | #[derive (Debug)] |
56 | pub struct Authenticated<S> { |
57 | pub(crate) conn: Connection<S>, |
58 | /// The server Guid |
59 | pub(crate) server_guid: Guid, |
60 | /// Whether file descriptor passing has been accepted by both sides |
61 | #[cfg (unix)] |
62 | pub(crate) cap_unix_fd: bool, |
63 | } |
64 | |
65 | impl<S> Authenticated<S> |
66 | where |
67 | S: Socket + Unpin, |
68 | { |
69 | /// Create a client-side `Authenticated` for the given `socket`. |
70 | pub async fn client(socket: S, mechanisms: Option<VecDeque<AuthMechanism>>) -> Result<Self> { |
71 | ClientHandshake::new(socket, mechanisms).perform().await |
72 | } |
73 | |
74 | /// Create a server-side `Authenticated` for the given `socket`. |
75 | /// |
76 | /// The function takes `client_uid` on Unix only. On Windows, it takes `client_sid` instead. |
77 | pub async fn server( |
78 | socket: S, |
79 | guid: Guid, |
80 | #[cfg (unix)] client_uid: Option<u32>, |
81 | #[cfg (windows)] client_sid: Option<String>, |
82 | auth_mechanisms: Option<VecDeque<AuthMechanism>>, |
83 | cookie_id: Option<usize>, |
84 | cookie_context: CookieContext<'_>, |
85 | ) -> Result<Self> { |
86 | ServerHandshake::new( |
87 | socket, |
88 | guid, |
89 | #[cfg (unix)] |
90 | client_uid, |
91 | #[cfg (windows)] |
92 | client_sid, |
93 | auth_mechanisms, |
94 | cookie_id, |
95 | cookie_context, |
96 | )? |
97 | .perform() |
98 | .await |
99 | } |
100 | } |
101 | |
102 | /* |
103 | * Client-side handshake logic |
104 | */ |
105 | |
106 | #[derive (Clone, Copy, Debug, PartialEq, Eq)] |
107 | #[allow (clippy::upper_case_acronyms)] |
108 | enum ClientHandshakeStep { |
109 | Init, |
110 | MechanismInit, |
111 | WaitingForData, |
112 | WaitingForOK, |
113 | WaitingForAgreeUnixFD, |
114 | Done, |
115 | } |
116 | |
117 | // The plain-text SASL profile authentication protocol described here: |
118 | // <https://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol> |
119 | // |
120 | // These are all the known commands, which can be parsed from or serialized to text. |
121 | #[derive (Debug)] |
122 | #[allow (clippy::upper_case_acronyms)] |
123 | enum Command { |
124 | Auth(Option<AuthMechanism>, Option<Vec<u8>>), |
125 | Cancel, |
126 | Begin, |
127 | Data(Option<Vec<u8>>), |
128 | Error(String), |
129 | NegotiateUnixFD, |
130 | Rejected(Vec<AuthMechanism>), |
131 | Ok(Guid), |
132 | AgreeUnixFD, |
133 | } |
134 | |
135 | /// A representation of an in-progress handshake, client-side |
136 | /// |
137 | /// This struct is an async-compatible representation of the initial handshake that must be |
138 | /// performed before a D-Bus connection can be used. To use it, you should call the |
139 | /// [`advance_handshake`] method whenever the underlying socket becomes ready (tracking the |
140 | /// readiness itself is not managed by this abstraction) until it returns `Ok(())`, at which point |
141 | /// you can invoke the [`try_finish`] method to get an [`Authenticated`], which can be given to |
142 | /// [`Connection::new_authenticated`]. |
143 | /// |
144 | /// [`advance_handshake`]: struct.ClientHandshake.html#method.advance_handshake |
145 | /// [`try_finish`]: struct.ClientHandshake.html#method.try_finish |
146 | /// [`Authenticated`]: struct.AUthenticated.html |
147 | /// [`Connection::new_authenticated`]: ../struct.Connection.html#method.new_authenticated |
148 | #[derive (Debug)] |
149 | pub struct ClientHandshake<S> { |
150 | common: HandshakeCommon<S>, |
151 | step: ClientHandshakeStep, |
152 | } |
153 | |
154 | #[async_trait ] |
155 | pub trait Handshake<S> { |
156 | /// Perform the handshake. |
157 | /// |
158 | /// On a successful handshake, you get an `Authenticated`. If you need to send a Bus Hello, |
159 | /// this remains to be done. |
160 | async fn perform(mut self) -> Result<Authenticated<S>>; |
161 | } |
162 | |
163 | impl<S: Socket> ClientHandshake<S> { |
164 | /// Start a handshake on this client socket |
165 | pub fn new(socket: S, mechanisms: Option<VecDeque<AuthMechanism>>) -> ClientHandshake<S> { |
166 | let mechanisms = mechanisms.unwrap_or_else(|| { |
167 | let mut mechanisms = VecDeque::new(); |
168 | mechanisms.push_back(AuthMechanism::External); |
169 | mechanisms.push_back(AuthMechanism::Cookie); |
170 | mechanisms.push_back(AuthMechanism::Anonymous); |
171 | mechanisms |
172 | }); |
173 | |
174 | ClientHandshake { |
175 | common: HandshakeCommon::new(socket, mechanisms, None), |
176 | step: ClientHandshakeStep::Init, |
177 | } |
178 | } |
179 | |
180 | fn mechanism_init(&mut self) -> Result<(ClientHandshakeStep, Command)> { |
181 | use ClientHandshakeStep::*; |
182 | let mech = self.common.mechanism()?; |
183 | match mech { |
184 | AuthMechanism::Anonymous => Ok(( |
185 | WaitingForOK, |
186 | Command::Auth(Some(*mech), Some("zbus" .into())), |
187 | )), |
188 | AuthMechanism::External => Ok(( |
189 | WaitingForOK, |
190 | Command::Auth(Some(*mech), Some(sasl_auth_id()?.into_bytes())), |
191 | )), |
192 | AuthMechanism::Cookie => Ok(( |
193 | WaitingForData, |
194 | Command::Auth(Some(*mech), Some(sasl_auth_id()?.into_bytes())), |
195 | )), |
196 | } |
197 | } |
198 | |
199 | async fn mechanism_data(&mut self, data: Vec<u8>) -> Result<(ClientHandshakeStep, Command)> { |
200 | let mech = self.common.mechanism()?; |
201 | match mech { |
202 | AuthMechanism::Cookie => { |
203 | let context = std::str::from_utf8(&data) |
204 | .map_err(|_| Error::Handshake("Cookie context was not valid UTF-8" .into()))?; |
205 | let mut split = context.split_ascii_whitespace(); |
206 | let context = split |
207 | .next() |
208 | .ok_or_else(|| Error::Handshake("Missing cookie context name" .into()))?; |
209 | let context = Str::from(context).try_into()?; |
210 | let id = split |
211 | .next() |
212 | .ok_or_else(|| Error::Handshake("Missing cookie ID" .into()))?; |
213 | let id = id |
214 | .parse() |
215 | .map_err(|e| Error::Handshake(format!("Invalid cookie ID ` {id}`: {e}" )))?; |
216 | let server_challenge = split |
217 | .next() |
218 | .ok_or_else(|| Error::Handshake("Missing cookie challenge" .into()))?; |
219 | |
220 | let cookie = Cookie::lookup(&context, id).await?.cookie; |
221 | let client_challenge = random_ascii(16); |
222 | let sec = format!(" {server_challenge}: {client_challenge}: {cookie}" ); |
223 | let sha1 = hex::encode(Sha1::digest(sec)); |
224 | let data = format!(" {client_challenge} {sha1}" ); |
225 | Ok(( |
226 | ClientHandshakeStep::WaitingForOK, |
227 | Command::Data(Some(data.into())), |
228 | )) |
229 | } |
230 | _ => Err(Error::Handshake("Unexpected mechanism DATA" .into())), |
231 | } |
232 | } |
233 | } |
234 | |
235 | fn random_ascii(len: usize) -> String { |
236 | use rand::{distributions::Alphanumeric, thread_rng, Rng}; |
237 | use std::iter; |
238 | |
239 | let mut rng: ThreadRng = thread_rng(); |
240 | iterimpl Iterator ::repeat(()) |
241 | .map(|()| rng.sample(distr:Alphanumeric)) |
242 | .map(char::from) |
243 | .take(len) |
244 | .collect() |
245 | } |
246 | |
247 | fn sasl_auth_id() -> Result<String> { |
248 | let id: String = { |
249 | #[cfg (unix)] |
250 | { |
251 | Uid::effective().to_string() |
252 | } |
253 | |
254 | #[cfg (windows)] |
255 | { |
256 | win32::ProcessToken::open(None)?.sid()? |
257 | } |
258 | }; |
259 | |
260 | Ok(id) |
261 | } |
262 | |
263 | #[derive (Debug)] |
264 | struct Cookie { |
265 | id: usize, |
266 | cookie: String, |
267 | } |
268 | |
269 | impl Cookie { |
270 | fn keyring_path() -> Result<PathBuf> { |
271 | let mut path = home_dir() |
272 | .ok_or_else(|| Error::Handshake("Failed to determine home directory" .into()))?; |
273 | path.push(".dbus-keyrings" ); |
274 | Ok(path) |
275 | } |
276 | |
277 | async fn read_keyring(context: &CookieContext<'_>) -> Result<Vec<Cookie>> { |
278 | let mut path = Cookie::keyring_path()?; |
279 | #[cfg (unix)] |
280 | { |
281 | use std::os::unix::fs::PermissionsExt; |
282 | |
283 | let perms = crate::file::metadata(&path).await?.permissions().mode(); |
284 | if perms & 0o066 != 0 { |
285 | return Err(Error::Handshake( |
286 | "DBus keyring has invalid permissions" .into(), |
287 | )); |
288 | } |
289 | } |
290 | #[cfg (not(unix))] |
291 | { |
292 | // FIXME: add code to check directory permissions |
293 | } |
294 | path.push(&*context.0); |
295 | trace!("Reading keyring {:?}" , path); |
296 | let mut lines = FileLines::open(&path).await?.enumerate(); |
297 | let mut cookies = vec![]; |
298 | while let Some((n, line)) = lines.next().await { |
299 | let line = line?; |
300 | let mut split = line.split_whitespace(); |
301 | let id = split |
302 | .next() |
303 | .ok_or_else(|| { |
304 | Error::Handshake(format!( |
305 | "DBus cookie ` {}` missing ID at line {n}" , |
306 | path.display(), |
307 | )) |
308 | })? |
309 | .parse() |
310 | .map_err(|e| { |
311 | Error::Handshake(format!( |
312 | "Failed to parse cookie ID in file ` {}` at line {n}: {e}" , |
313 | path.display(), |
314 | )) |
315 | })?; |
316 | let _ = split.next().ok_or_else(|| { |
317 | Error::Handshake(format!( |
318 | "DBus cookie ` {}` missing creation time at line {n}" , |
319 | path.display(), |
320 | )) |
321 | })?; |
322 | let cookie = split |
323 | .next() |
324 | .ok_or_else(|| { |
325 | Error::Handshake(format!( |
326 | "DBus cookie ` {}` missing cookie data at line {}" , |
327 | path.to_str().unwrap(), |
328 | n |
329 | )) |
330 | })? |
331 | .to_string(); |
332 | cookies.push(Cookie { id, cookie }) |
333 | } |
334 | trace!("Loaded keyring {:?}" , cookies); |
335 | Ok(cookies) |
336 | } |
337 | |
338 | async fn lookup(context: &CookieContext<'_>, id: usize) -> Result<Cookie> { |
339 | let keyring = Self::read_keyring(context).await?; |
340 | keyring |
341 | .into_iter() |
342 | .find(|c| c.id == id) |
343 | .ok_or_else(|| Error::Handshake(format!("DBus cookie ID {id} not found" ))) |
344 | } |
345 | |
346 | async fn first(context: &CookieContext<'_>) -> Result<Cookie> { |
347 | let keyring = Self::read_keyring(context).await?; |
348 | keyring |
349 | .into_iter() |
350 | .next() |
351 | .ok_or_else(|| Error::Handshake("No cookies available" .into())) |
352 | } |
353 | } |
354 | |
355 | #[derive (Debug)] |
356 | pub struct CookieContext<'c>(Str<'c>); |
357 | |
358 | impl<'c> TryFrom<Str<'c>> for CookieContext<'c> { |
359 | type Error = Error; |
360 | |
361 | fn try_from(value: Str<'c>) -> Result<Self> { |
362 | if value.is_empty() { |
363 | return Err(Error::Handshake("Empty cookie context" .into())); |
364 | } else if !value.is_ascii() || value.contains(['/' , ' \\' , ' ' , ' \n' , ' \r' , ' \t' , '.' ]) { |
365 | return Err(Error::Handshake( |
366 | "Invalid characters in cookie context" .into(), |
367 | )); |
368 | } |
369 | |
370 | Ok(Self(value)) |
371 | } |
372 | } |
373 | |
374 | impl Default for CookieContext<'_> { |
375 | fn default() -> Self { |
376 | Self(Str::from_static("org_freedesktop_general" )) |
377 | } |
378 | } |
379 | |
380 | #[async_trait ] |
381 | impl<S: Socket> Handshake<S> for ClientHandshake<S> { |
382 | #[instrument (skip(self))] |
383 | async fn perform(mut self) -> Result<Authenticated<S>> { |
384 | use ClientHandshakeStep::*; |
385 | loop { |
386 | let (next_step, cmd) = match self.step { |
387 | Init => { |
388 | trace!("Initializing" ); |
389 | #[allow (clippy::let_and_return)] |
390 | let ret = self.mechanism_init()?; |
391 | // The dbus daemon on some platforms requires sending the zero byte as a |
392 | // separate message with SCM_CREDS. |
393 | #[cfg (any(target_os = "freebsd" , target_os = "dragonfly" ))] |
394 | let written = self |
395 | .common |
396 | .socket |
397 | .send_zero_byte() |
398 | .map_err(|e| { |
399 | Error::Handshake(format!( |
400 | "Could not send zero byte with credentials: {}" , |
401 | e |
402 | )) |
403 | }) |
404 | .and_then(|n| match n { |
405 | None => Err(Error::Handshake( |
406 | "Could not send zero byte with credentials" .to_string(), |
407 | )), |
408 | Some(n) => Ok(n), |
409 | })?; |
410 | |
411 | // leading 0 is sent separately already for `freebsd` and `dragonfly` above. |
412 | #[cfg (not(any(target_os = "freebsd" , target_os = "dragonfly" )))] |
413 | let written = poll_fn(|cx| { |
414 | self.common.socket.poll_sendmsg( |
415 | cx, |
416 | &[b' \0' ], |
417 | #[cfg (unix)] |
418 | &[], |
419 | ) |
420 | }) |
421 | .await?; |
422 | |
423 | if written != 1 { |
424 | return Err(Error::Handshake( |
425 | "Could not send zero byte with credentials" .to_string(), |
426 | )); |
427 | } |
428 | |
429 | ret |
430 | } |
431 | MechanismInit => { |
432 | trace!("Initializing auth mechanisms" ); |
433 | self.mechanism_init()? |
434 | } |
435 | WaitingForData | WaitingForOK => { |
436 | trace!("Waiting for DATA or OK from server" ); |
437 | let reply = self.common.read_command().await?; |
438 | match (self.step, reply) { |
439 | (_, Command::Data(data)) => { |
440 | trace!("Received DATA from server" ); |
441 | let data = data.ok_or_else(|| { |
442 | Error::Handshake("Received DATA with no data from server" .into()) |
443 | })?; |
444 | self.mechanism_data(data).await? |
445 | } |
446 | (_, Command::Rejected(_)) => { |
447 | trace!("Received REJECT from server. Will try next auth mechanism.." ); |
448 | self.common.mechanisms.pop_front(); |
449 | self.step = MechanismInit; |
450 | continue; |
451 | } |
452 | (WaitingForOK, Command::Ok(guid)) => { |
453 | trace!("Received OK from server" ); |
454 | self.common.server_guid = Some(guid); |
455 | if self.common.socket.can_pass_unix_fd() { |
456 | (WaitingForAgreeUnixFD, Command::NegotiateUnixFD) |
457 | } else { |
458 | (Done, Command::Begin) |
459 | } |
460 | } |
461 | (_, reply) => { |
462 | return Err(Error::Handshake(format!( |
463 | "Unexpected server AUTH OK reply: {reply}" |
464 | ))); |
465 | } |
466 | } |
467 | } |
468 | WaitingForAgreeUnixFD => { |
469 | trace!("Waiting for Unix FD passing agreement from server" ); |
470 | let reply = self.common.read_command().await?; |
471 | match reply { |
472 | Command::AgreeUnixFD => { |
473 | trace!("Unix FD passing agreed by server" ); |
474 | self.common.cap_unix_fd = true |
475 | } |
476 | Command::Error(_) => { |
477 | trace!("Unix FD passing rejected by server" ); |
478 | self.common.cap_unix_fd = false |
479 | } |
480 | _ => { |
481 | return Err(Error::Handshake(format!( |
482 | "Unexpected server UNIX_FD reply: {reply}" |
483 | ))); |
484 | } |
485 | } |
486 | (Done, Command::Begin) |
487 | } |
488 | Done => { |
489 | trace!("Handshake done" ); |
490 | return Ok(Authenticated { |
491 | conn: Connection::new(self.common.socket, self.common.recv_buffer), |
492 | server_guid: self.common.server_guid.unwrap(), |
493 | #[cfg (unix)] |
494 | cap_unix_fd: self.common.cap_unix_fd, |
495 | }); |
496 | } |
497 | }; |
498 | self.common.write_command(cmd).await?; |
499 | self.step = next_step; |
500 | } |
501 | } |
502 | } |
503 | |
504 | /* |
505 | * Server-side handshake logic |
506 | */ |
507 | |
508 | #[derive (Debug)] |
509 | #[allow (clippy::upper_case_acronyms)] |
510 | enum ServerHandshakeStep { |
511 | WaitingForNull, |
512 | WaitingForAuth, |
513 | WaitingForData(AuthMechanism), |
514 | WaitingForBegin, |
515 | Done, |
516 | } |
517 | |
518 | /// A representation of an in-progress handshake, server-side |
519 | /// |
520 | /// This would typically be used to implement a D-Bus broker, or in the context of a P2P connection. |
521 | /// |
522 | /// This struct is an async-compatible representation of the initial handshake that must be |
523 | /// performed before a D-Bus connection can be used. To use it, you should call the |
524 | /// [`advance_handshake`] method whenever the underlying socket becomes ready (tracking the |
525 | /// readiness itself is not managed by this abstraction) until it returns `Ok(())`, at which point |
526 | /// you can invoke the [`try_finish`] method to get an [`Authenticated`], which can be given to |
527 | /// [`Connection::new_authenticated`]. |
528 | /// |
529 | /// [`advance_handshake`]: struct.ServerHandshake.html#method.advance_handshake |
530 | /// [`try_finish`]: struct.ServerHandshake.html#method.try_finish |
531 | /// [`Authenticated`]: struct.Authenticated.html |
532 | /// [`Connection::new_authenticated`]: ../struct.Connection.html#method.new_authenticated |
533 | #[derive (Debug)] |
534 | pub struct ServerHandshake<'s, S> { |
535 | common: HandshakeCommon<S>, |
536 | step: ServerHandshakeStep, |
537 | #[cfg (unix)] |
538 | client_uid: Option<u32>, |
539 | #[cfg (windows)] |
540 | client_sid: Option<String>, |
541 | cookie_id: Option<usize>, |
542 | cookie_context: CookieContext<'s>, |
543 | } |
544 | |
545 | impl<'s, S: Socket> ServerHandshake<'s, S> { |
546 | pub fn new( |
547 | socket: S, |
548 | guid: Guid, |
549 | #[cfg (unix)] client_uid: Option<u32>, |
550 | #[cfg (windows)] client_sid: Option<String>, |
551 | mechanisms: Option<VecDeque<AuthMechanism>>, |
552 | cookie_id: Option<usize>, |
553 | cookie_context: CookieContext<'s>, |
554 | ) -> Result<ServerHandshake<'s, S>> { |
555 | let mechanisms = match mechanisms { |
556 | Some(mechanisms) => mechanisms, |
557 | None => { |
558 | let mut mechanisms = VecDeque::new(); |
559 | mechanisms.push_back(AuthMechanism::External); |
560 | |
561 | mechanisms |
562 | } |
563 | }; |
564 | |
565 | Ok(ServerHandshake { |
566 | common: HandshakeCommon::new(socket, mechanisms, Some(guid)), |
567 | step: ServerHandshakeStep::WaitingForNull, |
568 | #[cfg (unix)] |
569 | client_uid, |
570 | #[cfg (windows)] |
571 | client_sid, |
572 | cookie_id, |
573 | cookie_context, |
574 | }) |
575 | } |
576 | |
577 | async fn auth_ok(&mut self) -> Result<()> { |
578 | let cmd = Command::Ok(self.guid().clone()); |
579 | trace!("Sending authentication OK" ); |
580 | self.common.write_command(cmd).await?; |
581 | self.step = ServerHandshakeStep::WaitingForBegin; |
582 | |
583 | Ok(()) |
584 | } |
585 | |
586 | async fn check_external_auth(&mut self, sasl_id: &[u8]) -> Result<()> { |
587 | let auth_ok = { |
588 | let id = std::str::from_utf8(sasl_id) |
589 | .map_err(|e| Error::Handshake(format!("Invalid ID: {e}" )))?; |
590 | #[cfg (unix)] |
591 | { |
592 | let uid = id |
593 | .parse::<u32>() |
594 | .map_err(|e| Error::Handshake(format!("Invalid UID: {e}" )))?; |
595 | self.client_uid.map(|u| u == uid).unwrap_or(false) |
596 | } |
597 | #[cfg (windows)] |
598 | { |
599 | self.client_sid.as_ref().map(|u| u == id).unwrap_or(false) |
600 | } |
601 | }; |
602 | |
603 | if auth_ok { |
604 | self.auth_ok().await |
605 | } else { |
606 | self.rejected_error().await |
607 | } |
608 | } |
609 | |
610 | async fn check_cookie_auth(&mut self, sasl_id: &[u8]) -> Result<()> { |
611 | let cookie = match self.cookie_id { |
612 | Some(cookie_id) => Cookie::lookup(&self.cookie_context, cookie_id).await?, |
613 | None => Cookie::first(&self.cookie_context).await?, |
614 | }; |
615 | let id = std::str::from_utf8(sasl_id) |
616 | .map_err(|e| Error::Handshake(format!("Invalid ID: {e}" )))?; |
617 | if sasl_auth_id()? != id { |
618 | // While the spec will make you believe that DBUS_COOKIE_SHA1 can be used to |
619 | // authenticate any user, it is not even possible (or correct) for the server to manage |
620 | // contents in random users' home directories. |
621 | // |
622 | // The dbus reference implementation also has the same limitation/behavior. |
623 | self.rejected_error().await?; |
624 | return Ok(()); |
625 | } |
626 | let server_challenge = random_ascii(16); |
627 | let data = format!(" {} {} {server_challenge}" , self.cookie_context.0, cookie.id); |
628 | let cmd = Command::Data(Some(data.into_bytes())); |
629 | trace!("Sending DBUS_COOKIE_SHA1 authentication challenge" ); |
630 | self.common.write_command(cmd).await?; |
631 | |
632 | let auth_data = match self.common.read_command().await? { |
633 | Command::Data(data) => data, |
634 | _ => None, |
635 | }; |
636 | let auth_data = auth_data.ok_or_else(|| { |
637 | Error::Handshake("Expected DBUS_COOKIE_SHA1 authentication challenge response" .into()) |
638 | })?; |
639 | let client_auth = std::str::from_utf8(&auth_data) |
640 | .map_err(|e| Error::Handshake(format!("Invalid COOKIE authentication data: {e}" )))?; |
641 | let mut split = client_auth.split_ascii_whitespace(); |
642 | let client_challenge = split |
643 | .next() |
644 | .ok_or_else(|| Error::Handshake("Missing cookie challenge" .into()))?; |
645 | let client_sha1 = split |
646 | .next() |
647 | .ok_or_else(|| Error::Handshake("Missing client cookie data" .into()))?; |
648 | let sec = format!(" {server_challenge}: {client_challenge}: {}" , cookie.cookie); |
649 | let sha1 = hex::encode(Sha1::digest(sec)); |
650 | |
651 | if sha1 == client_sha1 { |
652 | self.auth_ok().await |
653 | } else { |
654 | self.rejected_error().await |
655 | } |
656 | } |
657 | |
658 | async fn unsupported_command_error(&mut self) -> Result<()> { |
659 | let cmd = Command::Error("Unsupported command" .to_string()); |
660 | trace!("Sending authentication error" ); |
661 | self.common.write_command(cmd).await?; |
662 | self.step = ServerHandshakeStep::WaitingForAuth; |
663 | |
664 | Ok(()) |
665 | } |
666 | |
667 | async fn rejected_error(&mut self) -> Result<()> { |
668 | let mechanisms = self.common.mechanisms.iter().cloned().collect(); |
669 | let cmd = Command::Rejected(mechanisms); |
670 | trace!("Sending authentication error" ); |
671 | self.common.write_command(cmd).await?; |
672 | self.step = ServerHandshakeStep::WaitingForAuth; |
673 | |
674 | Ok(()) |
675 | } |
676 | |
677 | fn guid(&self) -> &Guid { |
678 | // SAFETY: We know that the server GUID is set because we set it in the constructor. |
679 | self.common |
680 | .server_guid |
681 | .as_ref() |
682 | .expect("Server GUID not set" ) |
683 | } |
684 | } |
685 | |
686 | #[async_trait ] |
687 | impl<S: Socket> Handshake<S> for ServerHandshake<'_, S> { |
688 | #[instrument (skip(self))] |
689 | async fn perform(mut self) -> Result<Authenticated<S>> { |
690 | loop { |
691 | match self.step { |
692 | ServerHandshakeStep::WaitingForNull => { |
693 | trace!("Waiting for NULL" ); |
694 | let mut buffer = [0; 1]; |
695 | let read = |
696 | poll_fn(|cx| self.common.socket.poll_recvmsg(cx, &mut buffer)).await?; |
697 | #[cfg (unix)] |
698 | let read = read.0; |
699 | // recvmsg cannot return anything else than Ok(1) or Err |
700 | debug_assert!(read == 1); |
701 | if buffer[0] != 0 { |
702 | return Err(Error::Handshake( |
703 | "First client byte is not NUL!" .to_string(), |
704 | )); |
705 | } |
706 | trace!("Received NULL from client" ); |
707 | self.step = ServerHandshakeStep::WaitingForAuth; |
708 | } |
709 | ServerHandshakeStep::WaitingForAuth => { |
710 | trace!("Waiting for authentication" ); |
711 | let reply = self.common.read_command().await?; |
712 | match reply { |
713 | Command::Auth(mech, resp) => { |
714 | let mech = mech.filter(|m| self.common.mechanisms.contains(m)); |
715 | |
716 | match (mech, &resp) { |
717 | (Some(mech), None) => { |
718 | trace!("Sending data request" ); |
719 | self.common.write_command(Command::Data(None)).await?; |
720 | self.step = ServerHandshakeStep::WaitingForData(mech); |
721 | } |
722 | (Some(AuthMechanism::Anonymous), Some(_)) => { |
723 | self.auth_ok().await?; |
724 | } |
725 | (Some(AuthMechanism::External), Some(sasl_id)) => { |
726 | self.check_external_auth(sasl_id).await?; |
727 | } |
728 | (Some(AuthMechanism::Cookie), Some(sasl_id)) => { |
729 | self.check_cookie_auth(sasl_id).await?; |
730 | } |
731 | _ => self.rejected_error().await?, |
732 | } |
733 | } |
734 | Command::Error(_) => self.rejected_error().await?, |
735 | Command::Begin => { |
736 | return Err(Error::Handshake( |
737 | "Received BEGIN while not authenticated" .to_string(), |
738 | )); |
739 | } |
740 | _ => self.unsupported_command_error().await?, |
741 | } |
742 | } |
743 | ServerHandshakeStep::WaitingForData(mech) => { |
744 | trace!("Waiting for authentication" ); |
745 | let reply = self.common.read_command().await?; |
746 | match (mech, reply) { |
747 | (AuthMechanism::External, Command::Data(None)) => self.auth_ok().await?, |
748 | (AuthMechanism::External, Command::Data(Some(data))) => { |
749 | self.check_external_auth(&data).await?; |
750 | } |
751 | (AuthMechanism::Anonymous, Command::Data(_)) => self.auth_ok().await?, |
752 | (_, Command::Data(_)) => self.rejected_error().await?, |
753 | (_, _) => self.unsupported_command_error().await?, |
754 | } |
755 | } |
756 | ServerHandshakeStep::WaitingForBegin => { |
757 | trace!("Waiting for Begin command from the client" ); |
758 | let reply = self.common.read_command().await?; |
759 | match reply { |
760 | Command::Begin => { |
761 | trace!("Received Begin command from the client" ); |
762 | self.step = ServerHandshakeStep::Done; |
763 | } |
764 | Command::Cancel | Command::Error(_) => { |
765 | trace!("Received CANCEL or ERROR command from the client" ); |
766 | self.rejected_error().await?; |
767 | } |
768 | #[cfg (unix)] |
769 | Command::NegotiateUnixFD => { |
770 | trace!("Received NEGOTIATE_UNIX_FD command from the client" ); |
771 | self.common.cap_unix_fd = true; |
772 | trace!("Sending AGREE_UNIX_FD to the client" ); |
773 | self.common.write_command(Command::AgreeUnixFD).await?; |
774 | self.step = ServerHandshakeStep::WaitingForBegin; |
775 | } |
776 | _ => self.unsupported_command_error().await?, |
777 | } |
778 | } |
779 | ServerHandshakeStep::Done => { |
780 | trace!("Handshake done" ); |
781 | return Ok(Authenticated { |
782 | conn: Connection::new(self.common.socket, self.common.recv_buffer), |
783 | // SAFETY: We know that the server GUID is set because we set it in the |
784 | // constructor. |
785 | server_guid: self.common.server_guid.expect("Server GUID not set" ), |
786 | #[cfg (unix)] |
787 | cap_unix_fd: self.common.cap_unix_fd, |
788 | }); |
789 | } |
790 | } |
791 | } |
792 | } |
793 | } |
794 | |
795 | impl fmt::Display for AuthMechanism { |
796 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
797 | let mech: &str = match self { |
798 | AuthMechanism::External => "EXTERNAL" , |
799 | AuthMechanism::Cookie => "DBUS_COOKIE_SHA1" , |
800 | AuthMechanism::Anonymous => "ANONYMOUS" , |
801 | }; |
802 | write!(f, " {mech}" ) |
803 | } |
804 | } |
805 | |
806 | impl FromStr for AuthMechanism { |
807 | type Err = Error; |
808 | |
809 | fn from_str(s: &str) -> Result<Self> { |
810 | match s { |
811 | "EXTERNAL" => Ok(AuthMechanism::External), |
812 | "DBUS_COOKIE_SHA1" => Ok(AuthMechanism::Cookie), |
813 | "ANONYMOUS" => Ok(AuthMechanism::Anonymous), |
814 | _ => Err(Error::Handshake(format!("Unknown mechanism: {s}" ))), |
815 | } |
816 | } |
817 | } |
818 | |
819 | impl From<Command> for Vec<u8> { |
820 | fn from(c: Command) -> Self { |
821 | c.to_string().into() |
822 | } |
823 | } |
824 | |
825 | impl fmt::Display for Command { |
826 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
827 | match self { |
828 | Command::Auth(mech, resp) => match (mech, resp) { |
829 | (Some(mech), Some(resp)) => write!(f, "AUTH {mech} {}" , hex::encode(resp)), |
830 | (Some(mech), None) => write!(f, "AUTH {mech}" ), |
831 | _ => write!(f, "AUTH" ), |
832 | }, |
833 | Command::Cancel => write!(f, "CANCEL" ), |
834 | Command::Begin => write!(f, "BEGIN" ), |
835 | Command::Data(data) => match data { |
836 | None => write!(f, "DATA" ), |
837 | Some(data) => write!(f, "DATA {}" , hex::encode(data)), |
838 | }, |
839 | Command::Error(expl) => write!(f, "ERROR {expl}" ), |
840 | Command::NegotiateUnixFD => write!(f, "NEGOTIATE_UNIX_FD" ), |
841 | Command::Rejected(mechs) => { |
842 | write!( |
843 | f, |
844 | "REJECTED {}" , |
845 | mechs |
846 | .iter() |
847 | .map(|m| m.to_string()) |
848 | .collect::<Vec<_>>() |
849 | .join(" " ) |
850 | ) |
851 | } |
852 | Command::Ok(guid) => write!(f, "OK {guid}" ), |
853 | Command::AgreeUnixFD => write!(f, "AGREE_UNIX_FD" ), |
854 | }?; |
855 | write!(f, " \r\n" ) |
856 | } |
857 | } |
858 | |
859 | impl From<hex::FromHexError> for Error { |
860 | fn from(e: hex::FromHexError) -> Self { |
861 | Error::Handshake(format!("Invalid hexcode: {e}" )) |
862 | } |
863 | } |
864 | |
865 | impl FromStr for Command { |
866 | type Err = Error; |
867 | |
868 | fn from_str(s: &str) -> Result<Self> { |
869 | let mut words = s.split_ascii_whitespace(); |
870 | let cmd = match words.next() { |
871 | Some("AUTH" ) => { |
872 | let mech = if let Some(m) = words.next() { |
873 | Some(m.parse()?) |
874 | } else { |
875 | None |
876 | }; |
877 | let resp = match words.next() { |
878 | Some(resp) => Some(hex::decode(resp)?), |
879 | None => None, |
880 | }; |
881 | Command::Auth(mech, resp) |
882 | } |
883 | Some("CANCEL" ) => Command::Cancel, |
884 | Some("BEGIN" ) => Command::Begin, |
885 | Some("DATA" ) => { |
886 | let data = match words.next() { |
887 | Some(data) => Some(hex::decode(data)?), |
888 | None => None, |
889 | }; |
890 | |
891 | Command::Data(data) |
892 | } |
893 | Some("ERROR" ) => Command::Error(s.into()), |
894 | Some("NEGOTIATE_UNIX_FD" ) => Command::NegotiateUnixFD, |
895 | Some("REJECTED" ) => { |
896 | let mechs = words.map(|m| m.parse()).collect::<Result<_>>()?; |
897 | Command::Rejected(mechs) |
898 | } |
899 | Some("OK" ) => { |
900 | let guid = words |
901 | .next() |
902 | .ok_or_else(|| Error::Handshake("Missing OK server GUID!" .into()))?; |
903 | Command::Ok(guid.parse()?) |
904 | } |
905 | Some("AGREE_UNIX_FD" ) => Command::AgreeUnixFD, |
906 | _ => return Err(Error::Handshake(format!("Unknown command: {s}" ))), |
907 | }; |
908 | Ok(cmd) |
909 | } |
910 | } |
911 | |
912 | // Common code for the client and server side of the handshake. |
913 | #[derive (Debug)] |
914 | pub struct HandshakeCommon<S> { |
915 | socket: S, |
916 | recv_buffer: Vec<u8>, |
917 | server_guid: Option<Guid>, |
918 | cap_unix_fd: bool, |
919 | // the current AUTH mechanism is front, ordered by priority |
920 | mechanisms: VecDeque<AuthMechanism>, |
921 | } |
922 | |
923 | impl<S: Socket> HandshakeCommon<S> { |
924 | /// Start a handshake on this client socket |
925 | pub fn new(socket: S, mechanisms: VecDeque<AuthMechanism>, server_guid: Option<Guid>) -> Self { |
926 | Self { |
927 | socket, |
928 | recv_buffer: Vec::new(), |
929 | server_guid, |
930 | cap_unix_fd: false, |
931 | mechanisms, |
932 | } |
933 | } |
934 | |
935 | #[instrument (skip(self))] |
936 | async fn write_command(&mut self, command: Command) -> Result<()> { |
937 | let mut send_buffer = Vec::<u8>::from(command); |
938 | while !send_buffer.is_empty() { |
939 | let written = poll_fn(|cx| { |
940 | self.socket.poll_sendmsg( |
941 | cx, |
942 | &send_buffer, |
943 | #[cfg (unix)] |
944 | &[], |
945 | ) |
946 | }) |
947 | .await?; |
948 | send_buffer.drain(..written); |
949 | } |
950 | Ok(()) |
951 | } |
952 | |
953 | #[instrument (skip(self))] |
954 | async fn read_command(&mut self) -> Result<Command> { |
955 | let mut cmd_end = 0; |
956 | loop { |
957 | if let Some(i) = self.recv_buffer[cmd_end..].iter().position(|b| *b == b' \n' ) { |
958 | if cmd_end + i == 0 || self.recv_buffer.get(cmd_end + i - 1) != Some(&b' \r' ) { |
959 | return Err(Error::Handshake("Invalid line ending in handshake" .into())); |
960 | } |
961 | cmd_end += i + 1; |
962 | |
963 | break; |
964 | } else { |
965 | cmd_end = self.recv_buffer.len(); |
966 | } |
967 | |
968 | let mut buf = [0; 64]; |
969 | let res = poll_fn(|cx| self.socket.poll_recvmsg(cx, &mut buf)).await?; |
970 | let read = { |
971 | #[cfg (unix)] |
972 | { |
973 | let (read, fds) = res; |
974 | if !fds.is_empty() { |
975 | return Err(Error::Handshake("Unexpected FDs during handshake" .into())); |
976 | } |
977 | read |
978 | } |
979 | #[cfg (not(unix))] |
980 | { |
981 | res |
982 | } |
983 | }; |
984 | if read == 0 { |
985 | return Err(Error::Handshake("Unexpected EOF during handshake" .into())); |
986 | } |
987 | self.recv_buffer.extend(&buf[..read]); |
988 | } |
989 | |
990 | let line_bytes = self.recv_buffer.drain(..cmd_end); |
991 | let line = std::str::from_utf8(line_bytes.as_slice()) |
992 | .map_err(|e| Error::Handshake(e.to_string()))?; |
993 | |
994 | line.parse() |
995 | } |
996 | |
997 | fn mechanism(&self) -> Result<&AuthMechanism> { |
998 | self.mechanisms |
999 | .front() |
1000 | .ok_or_else(|| Error::Handshake("Exhausted available AUTH mechanisms" .into())) |
1001 | } |
1002 | } |
1003 | |
1004 | #[cfg (unix)] |
1005 | #[cfg (test)] |
1006 | mod tests { |
1007 | #[cfg (not(feature = "tokio" ))] |
1008 | use async_std::io::{Write as AsyncWrite, WriteExt}; |
1009 | use futures_util::future::join; |
1010 | use ntest::timeout; |
1011 | #[cfg (not(feature = "tokio" ))] |
1012 | use std::os::unix::net::UnixStream; |
1013 | use test_log::test; |
1014 | #[cfg (feature = "tokio" )] |
1015 | use tokio::{ |
1016 | io::{AsyncWrite, AsyncWriteExt}, |
1017 | net::UnixStream, |
1018 | }; |
1019 | |
1020 | use super::*; |
1021 | |
1022 | use crate::Guid; |
1023 | |
1024 | fn create_async_socket_pair() -> (impl AsyncWrite + Socket, impl AsyncWrite + Socket) { |
1025 | // Tokio needs us to call the sync function from async context. :shrug: |
1026 | let (p0, p1) = crate::utils::block_on(async { UnixStream::pair().unwrap() }); |
1027 | |
1028 | // initialize both handshakes |
1029 | #[cfg (not(feature = "tokio" ))] |
1030 | let (p0, p1) = { |
1031 | p0.set_nonblocking(true).unwrap(); |
1032 | p1.set_nonblocking(true).unwrap(); |
1033 | |
1034 | ( |
1035 | async_io::Async::new(p0).unwrap(), |
1036 | async_io::Async::new(p1).unwrap(), |
1037 | ) |
1038 | }; |
1039 | |
1040 | (p0, p1) |
1041 | } |
1042 | |
1043 | #[test ] |
1044 | fn handshake() { |
1045 | let (p0, p1) = create_async_socket_pair(); |
1046 | |
1047 | let client = ClientHandshake::new(p0, None); |
1048 | let server = ServerHandshake::new( |
1049 | p1, |
1050 | Guid::generate(), |
1051 | Some(Uid::effective().into()), |
1052 | None, |
1053 | None, |
1054 | CookieContext::default(), |
1055 | ) |
1056 | .unwrap(); |
1057 | |
1058 | // proceed to the handshakes |
1059 | let (client, server) = crate::utils::block_on(join( |
1060 | async move { client.perform().await.unwrap() }, |
1061 | async move { server.perform().await.unwrap() }, |
1062 | )); |
1063 | |
1064 | assert_eq!(client.server_guid, server.server_guid); |
1065 | assert_eq!(client.cap_unix_fd, server.cap_unix_fd); |
1066 | } |
1067 | |
1068 | #[test ] |
1069 | #[timeout(15000)] |
1070 | fn pipelined_handshake() { |
1071 | let (mut p0, p1) = create_async_socket_pair(); |
1072 | let server = ServerHandshake::new( |
1073 | p1, |
1074 | Guid::generate(), |
1075 | Some(Uid::effective().into()), |
1076 | None, |
1077 | None, |
1078 | CookieContext::default(), |
1079 | ) |
1080 | .unwrap(); |
1081 | |
1082 | crate::utils::block_on( |
1083 | p0.write_all( |
1084 | format!( |
1085 | " \0AUTH EXTERNAL {}\r\nNEGOTIATE_UNIX_FD \r\nBEGIN \r\n" , |
1086 | hex::encode(sasl_auth_id().unwrap()) |
1087 | ) |
1088 | .as_bytes(), |
1089 | ), |
1090 | ) |
1091 | .unwrap(); |
1092 | let server = crate::utils::block_on(server.perform()).unwrap(); |
1093 | |
1094 | assert!(server.cap_unix_fd); |
1095 | } |
1096 | |
1097 | #[test ] |
1098 | #[timeout(15000)] |
1099 | fn separate_external_data() { |
1100 | let (mut p0, p1) = create_async_socket_pair(); |
1101 | let server = ServerHandshake::new( |
1102 | p1, |
1103 | Guid::generate(), |
1104 | Some(Uid::effective().into()), |
1105 | None, |
1106 | None, |
1107 | CookieContext::default(), |
1108 | ) |
1109 | .unwrap(); |
1110 | |
1111 | crate::utils::block_on( |
1112 | p0.write_all( |
1113 | format!( |
1114 | " \0AUTH EXTERNAL \r\nDATA {}\r\nBEGIN \r\n" , |
1115 | hex::encode(sasl_auth_id().unwrap()) |
1116 | ) |
1117 | .as_bytes(), |
1118 | ), |
1119 | ) |
1120 | .unwrap(); |
1121 | crate::utils::block_on(server.perform()).unwrap(); |
1122 | } |
1123 | |
1124 | #[test ] |
1125 | #[timeout(15000)] |
1126 | fn missing_external_data() { |
1127 | let (mut p0, p1) = create_async_socket_pair(); |
1128 | let server = ServerHandshake::new( |
1129 | p1, |
1130 | Guid::generate(), |
1131 | Some(Uid::effective().into()), |
1132 | None, |
1133 | None, |
1134 | CookieContext::default(), |
1135 | ) |
1136 | .unwrap(); |
1137 | |
1138 | crate::utils::block_on(p0.write_all(b" \0AUTH EXTERNAL \r\nDATA \r\nBEGIN \r\n" )).unwrap(); |
1139 | crate::utils::block_on(server.perform()).unwrap(); |
1140 | } |
1141 | |
1142 | #[test ] |
1143 | #[timeout(15000)] |
1144 | fn anonymous_handshake() { |
1145 | let (mut p0, p1) = create_async_socket_pair(); |
1146 | let server = ServerHandshake::new( |
1147 | p1, |
1148 | Guid::generate(), |
1149 | Some(Uid::effective().into()), |
1150 | Some(vec![AuthMechanism::Anonymous].into()), |
1151 | None, |
1152 | CookieContext::default(), |
1153 | ) |
1154 | .unwrap(); |
1155 | |
1156 | crate::utils::block_on(p0.write_all(b" \0AUTH ANONYMOUS abcd \r\nBEGIN \r\n" )).unwrap(); |
1157 | crate::utils::block_on(server.perform()).unwrap(); |
1158 | } |
1159 | |
1160 | #[test ] |
1161 | #[timeout(15000)] |
1162 | fn separate_anonymous_data() { |
1163 | let (mut p0, p1) = create_async_socket_pair(); |
1164 | let server = ServerHandshake::new( |
1165 | p1, |
1166 | Guid::generate(), |
1167 | Some(Uid::effective().into()), |
1168 | Some(vec![AuthMechanism::Anonymous].into()), |
1169 | None, |
1170 | CookieContext::default(), |
1171 | ) |
1172 | .unwrap(); |
1173 | |
1174 | crate::utils::block_on(p0.write_all(b" \0AUTH ANONYMOUS \r\nDATA abcd \r\nBEGIN \r\n" )) |
1175 | .unwrap(); |
1176 | crate::utils::block_on(server.perform()).unwrap(); |
1177 | } |
1178 | } |
1179 | |