zbus/connection/handshake/
mod.rs

1mod auth_mechanism;
2mod client;
3mod command;
4mod common;
5mod cookies;
6#[cfg(feature = "p2p")]
7mod server;
8
9use async_trait::async_trait;
10#[cfg(unix)]
11use nix::unistd::Uid;
12use std::{collections::VecDeque, fmt::Debug};
13use zbus_names::OwnedUniqueName;
14use zvariant::Str;
15
16#[cfg(windows)]
17use crate::win32;
18use crate::{Error, OwnedGuid, Result};
19
20use super::socket::{BoxedSplit, ReadHalf, WriteHalf};
21
22pub use auth_mechanism::AuthMechanism;
23use client::Client;
24use command::Command;
25use common::Common;
26use cookies::Cookie;
27pub(crate) use cookies::CookieContext;
28#[cfg(feature = "p2p")]
29use server::Server;
30
31/// The result of a finalized handshake
32///
33/// The result of a finalized [`ClientHandshake`] or [`ServerHandshake`].
34///
35/// [`ClientHandshake`]: struct.ClientHandshake.html
36/// [`ServerHandshake`]: struct.ServerHandshake.html
37#[derive(Debug)]
38pub struct Authenticated {
39    pub(crate) socket_write: Box<dyn WriteHalf>,
40    /// The server Guid
41    pub(crate) server_guid: OwnedGuid,
42    /// Whether file descriptor passing has been accepted by both sides
43    #[cfg(unix)]
44    pub(crate) cap_unix_fd: bool,
45
46    pub(crate) socket_read: Option<Box<dyn ReadHalf>>,
47    pub(crate) already_received_bytes: Vec<u8>,
48    #[cfg(unix)]
49    pub(crate) already_received_fds: Vec<std::os::fd::OwnedFd>,
50    pub(crate) unique_name: Option<OwnedUniqueName>,
51}
52
53impl Authenticated {
54    /// Create a client-side `Authenticated` for the given `socket`.
55    pub async fn client(
56        socket: BoxedSplit,
57        server_guid: Option<OwnedGuid>,
58        mechanisms: Option<VecDeque<AuthMechanism>>,
59        bus: bool,
60    ) -> Result<Self> {
61        Client::new(socket, mechanisms, server_guid, bus)
62            .perform()
63            .await
64    }
65
66    /// Create a server-side `Authenticated` for the given `socket`.
67    ///
68    /// The function takes `client_uid` on Unix only. On Windows, it takes `client_sid` instead.
69    #[cfg(feature = "p2p")]
70    pub async fn server(
71        socket: BoxedSplit,
72        guid: OwnedGuid,
73        #[cfg(unix)] client_uid: Option<u32>,
74        #[cfg(windows)] client_sid: Option<String>,
75        auth_mechanisms: Option<VecDeque<AuthMechanism>>,
76        cookie_id: Option<usize>,
77        cookie_context: CookieContext<'_>,
78        unique_name: Option<OwnedUniqueName>,
79    ) -> Result<Self> {
80        Server::new(
81            socket,
82            guid,
83            #[cfg(unix)]
84            client_uid,
85            #[cfg(windows)]
86            client_sid,
87            auth_mechanisms,
88            cookie_id,
89            cookie_context,
90            unique_name,
91        )?
92        .perform()
93        .await
94    }
95}
96
97#[async_trait]
98pub trait Handshake {
99    /// Perform the handshake.
100    ///
101    /// On a successful handshake, you get an `Authenticated`. If you need to send a Bus Hello,
102    /// this remains to be done.
103    async fn perform(mut self) -> Result<Authenticated>;
104}
105
106fn random_ascii(len: usize) -> String {
107    use rand::{distributions::Alphanumeric, thread_rng, Rng};
108    use std::iter;
109
110    let mut rng = thread_rng();
111    iter::repeat(())
112        .map(|()| rng.sample(Alphanumeric))
113        .map(char::from)
114        .take(len)
115        .collect()
116}
117
118fn sasl_auth_id() -> Result<String> {
119    let id = {
120        #[cfg(unix)]
121        {
122            Uid::effective().to_string()
123        }
124
125        #[cfg(windows)]
126        {
127            win32::ProcessToken::open(None)?.sid()?
128        }
129    };
130
131    Ok(id)
132}
133
134#[cfg(feature = "p2p")]
135#[cfg(unix)]
136#[cfg(test)]
137mod tests {
138    use futures_util::future::join;
139    #[cfg(not(feature = "tokio"))]
140    use futures_util::io::{AsyncWrite, AsyncWriteExt};
141    use ntest::timeout;
142    #[cfg(not(feature = "tokio"))]
143    use std::os::unix::net::UnixStream;
144    use test_log::test;
145    #[cfg(feature = "tokio")]
146    use tokio::{
147        io::{AsyncWrite, AsyncWriteExt},
148        net::UnixStream,
149    };
150
151    use super::*;
152
153    use crate::{Guid, Socket};
154
155    fn create_async_socket_pair() -> (impl AsyncWrite + Socket, impl AsyncWrite + Socket) {
156        // Tokio needs us to call the sync function from async context. :shrug:
157        let (p0, p1) = crate::utils::block_on(async { UnixStream::pair().unwrap() });
158
159        // initialize both handshakes
160        #[cfg(not(feature = "tokio"))]
161        let (p0, p1) = {
162            p0.set_nonblocking(true).unwrap();
163            p1.set_nonblocking(true).unwrap();
164
165            (
166                async_io::Async::new(p0).unwrap(),
167                async_io::Async::new(p1).unwrap(),
168            )
169        };
170
171        (p0, p1)
172    }
173
174    #[test]
175    #[timeout(15000)]
176    fn handshake() {
177        let (p0, p1) = create_async_socket_pair();
178
179        let guid = OwnedGuid::from(Guid::generate());
180        let client = Client::new(p0.into(), None, Some(guid.clone()), false);
181        let server = Server::new(
182            p1.into(),
183            guid,
184            Some(Uid::effective().into()),
185            None,
186            None,
187            CookieContext::default(),
188            None,
189        )
190        .unwrap();
191
192        // proceed to the handshakes
193        let (client, server) = crate::utils::block_on(join(
194            async move { client.perform().await.unwrap() },
195            async move { server.perform().await.unwrap() },
196        ));
197
198        assert_eq!(client.server_guid, server.server_guid);
199        assert_eq!(client.cap_unix_fd, server.cap_unix_fd);
200    }
201
202    #[test]
203    #[timeout(15000)]
204    fn pipelined_handshake() {
205        let (mut p0, p1) = create_async_socket_pair();
206        let server = Server::new(
207            p1.into(),
208            Guid::generate().into(),
209            Some(Uid::effective().into()),
210            None,
211            None,
212            CookieContext::default(),
213            None,
214        )
215        .unwrap();
216
217        crate::utils::block_on(
218            p0.write_all(
219                format!(
220                    "\0AUTH EXTERNAL {}\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n",
221                    hex::encode(sasl_auth_id().unwrap())
222                )
223                .as_bytes(),
224            ),
225        )
226        .unwrap();
227        let server = crate::utils::block_on(server.perform()).unwrap();
228
229        assert!(server.cap_unix_fd);
230    }
231
232    #[test]
233    #[timeout(15000)]
234    fn separate_external_data() {
235        let (mut p0, p1) = create_async_socket_pair();
236        let server = Server::new(
237            p1.into(),
238            Guid::generate().into(),
239            Some(Uid::effective().into()),
240            None,
241            None,
242            CookieContext::default(),
243            None,
244        )
245        .unwrap();
246
247        crate::utils::block_on(
248            p0.write_all(
249                format!(
250                    "\0AUTH EXTERNAL\r\nDATA {}\r\nBEGIN\r\n",
251                    hex::encode(sasl_auth_id().unwrap())
252                )
253                .as_bytes(),
254            ),
255        )
256        .unwrap();
257        crate::utils::block_on(server.perform()).unwrap();
258    }
259
260    #[test]
261    #[timeout(15000)]
262    fn missing_external_data() {
263        let (mut p0, p1) = create_async_socket_pair();
264        let server = Server::new(
265            p1.into(),
266            Guid::generate().into(),
267            Some(Uid::effective().into()),
268            None,
269            None,
270            CookieContext::default(),
271            None,
272        )
273        .unwrap();
274
275        crate::utils::block_on(p0.write_all(b"\0AUTH EXTERNAL\r\nDATA\r\nBEGIN\r\n")).unwrap();
276        crate::utils::block_on(server.perform()).unwrap();
277    }
278
279    #[test]
280    #[timeout(15000)]
281    fn anonymous_handshake() {
282        let (mut p0, p1) = create_async_socket_pair();
283        let server = Server::new(
284            p1.into(),
285            Guid::generate().into(),
286            Some(Uid::effective().into()),
287            Some(vec![AuthMechanism::Anonymous].into()),
288            None,
289            CookieContext::default(),
290            None,
291        )
292        .unwrap();
293
294        crate::utils::block_on(p0.write_all(b"\0AUTH ANONYMOUS abcd\r\nBEGIN\r\n")).unwrap();
295        crate::utils::block_on(server.perform()).unwrap();
296    }
297
298    #[test]
299    #[timeout(15000)]
300    fn separate_anonymous_data() {
301        let (mut p0, p1) = create_async_socket_pair();
302        let server = Server::new(
303            p1.into(),
304            Guid::generate().into(),
305            Some(Uid::effective().into()),
306            Some(vec![AuthMechanism::Anonymous].into()),
307            None,
308            CookieContext::default(),
309            None,
310        )
311        .unwrap();
312
313        crate::utils::block_on(p0.write_all(b"\0AUTH ANONYMOUS\r\nDATA abcd\r\nBEGIN\r\n"))
314            .unwrap();
315        crate::utils::block_on(server.perform()).unwrap();
316    }
317}