1 | mod auth_mechanism; |
2 | mod client; |
3 | mod command; |
4 | mod common; |
5 | mod cookies; |
6 | #[cfg (feature = "p2p" )] |
7 | mod server; |
8 | |
9 | use async_trait::async_trait; |
10 | #[cfg (unix)] |
11 | use nix::unistd::Uid; |
12 | use std::{collections::VecDeque, fmt::Debug}; |
13 | use zbus_names::OwnedUniqueName; |
14 | use zvariant::Str; |
15 | |
16 | #[cfg (windows)] |
17 | use crate::win32; |
18 | use crate::{Error, OwnedGuid, Result}; |
19 | |
20 | use super::socket::{BoxedSplit, ReadHalf, WriteHalf}; |
21 | |
22 | pub use auth_mechanism::AuthMechanism; |
23 | use client::Client; |
24 | use command::Command; |
25 | use common::Common; |
26 | use cookies::Cookie; |
27 | pub(crate) use cookies::CookieContext; |
28 | #[cfg (feature = "p2p" )] |
29 | use 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)] |
38 | pub 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 | |
53 | impl 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 ] |
98 | pub 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 | |
106 | fn random_ascii(len: usize) -> String { |
107 | use rand::{distributions::Alphanumeric, thread_rng, Rng}; |
108 | use std::iter; |
109 | |
110 | let mut rng: ThreadRng = thread_rng(); |
111 | iterimpl Iterator ::repeat(()) |
112 | .map(|()| rng.sample(distr:Alphanumeric)) |
113 | .map(char::from) |
114 | .take(len) |
115 | .collect() |
116 | } |
117 | |
118 | fn sasl_auth_id() -> Result<String> { |
119 | let id: String = { |
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)] |
137 | mod 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 | } |
318 | |