| 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 | |