1//! Helpers for working with `~/.Xauthority`.
2
3#![cfg(feature = "std")]
4
5use alloc::string::ToString;
6use alloc::vec::Vec;
7use std::io::Error;
8
9use crate::protocol::xproto::Family as X11Family;
10
11const MIT_MAGIC_COOKIE_1: &[u8] = b"MIT-MAGIC-COOKIE-1";
12
13/// A family describes how to interpret some bytes as an address in an `AuthEntry`.
14///
15/// Compared to [`super::protocol::xproto::Family`], this is a `u16` and not an `u8` since
16/// that's what is used in `~/.Xauthority` files.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct Family(u16);
19
20impl Family {
21 /// IPv4 connection to the server
22 pub const INTERNET: Self = Self(0);
23 /// DECnet
24 pub const DEC_NET: Self = Self(1);
25 /// Chaosnet connection
26 pub const CHAOS: Self = Self(2);
27 /// Family without predefined meaning, but interpreted by the server, for example a user name
28 pub const SERVER_INTERPRETED: Self = Self(5);
29 /// IPv6 connection to the server
30 pub const INTERNET6: Self = Self(6);
31 /// Wildcard matching any protocol family
32 pub const WILD: Self = Self(65535);
33 /// For local non-net authentication
34 pub const LOCAL: Self = Self(256);
35 /// TODO: No idea what this means exactly
36 pub const NETNAME: Self = Self(254);
37 /// Kerberos 5 principal name
38 pub const KRB5_PRINCIPAL: Self = Self(253);
39 /// For local non-net authentication
40 pub const LOCAL_HOST: Self = Self(252);
41}
42
43impl From<X11Family> for Family {
44 fn from(value: X11Family) -> Self {
45 Self(value.into())
46 }
47}
48
49impl From<u16> for Family {
50 fn from(value: u16) -> Self {
51 Self(value)
52 }
53}
54
55/// A single entry of an `.Xauthority` file.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub(crate) struct AuthEntry {
58 /// The protocol family to which the entry applies
59 family: Family,
60 /// The address of the peer in a family-specific format
61 address: Vec<u8>,
62 /// The display number
63 number: Vec<u8>,
64 /// The name of the authentication method to use for the X11 server described by the previous
65 /// fields.
66 name: Vec<u8>,
67 /// Extra data for the authentication method.
68 data: Vec<u8>,
69}
70
71mod file {
72 //! Code for actually reading `~/.Xauthority`.
73
74 use alloc::{vec, vec::Vec};
75 use std::env::var_os;
76 use std::fs::File;
77 use std::io::{BufReader, Error, ErrorKind, Read};
78 use std::path::PathBuf;
79
80 use super::AuthEntry;
81
82 /// Read a single `u16` from an `~/.Xauthority` file.
83 ///
84 /// The file stores these entries in big endian.
85 fn read_u16<R: Read>(read: &mut R) -> Result<u16, Error> {
86 let mut buffer = [0; 2];
87 read.read_exact(&mut buffer)?;
88 Ok(u16::from_be_bytes(buffer))
89 }
90
91 /// Read a single "byte array" from an `~/.Xauthority` file.
92 ///
93 /// The file stores these as a length field followed by a number of bytes that contain the
94 /// actual data.
95 fn read_string<R: Read>(read: &mut R) -> Result<Vec<u8>, Error> {
96 let length = read_u16(read)?;
97 let mut result = vec![0; length.into()];
98 read.read_exact(&mut result[..])?;
99 Ok(result)
100 }
101
102 /// Read a single entry from an `~/.Xauthority` file.
103 ///
104 /// This function tries to return `Ok(None)` when the end of the file is reached. However, the
105 /// code also treats a single byte as 'end of file', because things were simpler to implement
106 /// like this.
107 fn read_entry<R: Read>(read: &mut R) -> Result<Option<AuthEntry>, Error> {
108 let family = match read_u16(read) {
109 Ok(family) => family,
110 Err(ref e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None),
111 Err(e) => return Err(e),
112 }
113 .into();
114 let address = read_string(read)?;
115 let number = read_string(read)?;
116 let name = read_string(read)?;
117 let data = read_string(read)?;
118 Ok(Some(AuthEntry {
119 family,
120 address,
121 number,
122 name,
123 data,
124 }))
125 }
126
127 /// Get the file name for `~/.Xauthority` based on environment variables.
128 ///
129 /// The code in libXau contains a special case for Windows (looks like cygwin) that is not
130 /// handled here (yet?).
131 fn get_xauthority_file_name() -> Option<PathBuf> {
132 if let Some(name) = var_os("XAUTHORITY") {
133 return Some(name.into());
134 }
135 var_os("HOME").map(|prefix| {
136 let mut result = PathBuf::new();
137 result.push(prefix);
138 result.push(".Xauthority");
139 result
140 })
141 }
142
143 /// An iterator over the entries of an `.Xauthority` file
144 #[derive(Debug)]
145 pub(crate) struct XAuthorityEntries(BufReader<File>);
146
147 impl XAuthorityEntries {
148 /// Open `~/.Xauthority` for reading.
149 ///
150 /// This function returns `Ok(None)` when the location of the `.Xauthority` file could not
151 /// be determined. If opening the file failed (for example, because it does not exist),
152 /// that error is returned.
153 pub(crate) fn new() -> Result<Option<XAuthorityEntries>, Error> {
154 get_xauthority_file_name()
155 .map(File::open)
156 .transpose()?
157 // At this point we have Option<File> and errors while opening the file were
158 // returned to the caller.
159 .map(|file| Ok(XAuthorityEntries(BufReader::new(file))))
160 .transpose()
161 }
162 }
163
164 impl Iterator for XAuthorityEntries {
165 type Item = Result<AuthEntry, Error>;
166
167 fn next(&mut self) -> Option<Self::Item> {
168 read_entry(&mut self.0).transpose()
169 }
170 }
171
172 #[cfg(test)]
173 mod test {
174 use super::super::{AuthEntry, Family};
175 use super::read_entry;
176 use alloc::vec;
177 use std::io::Cursor;
178
179 #[test]
180 fn test_read() {
181 // Data generated via xauth -f /tmp/file add :1 bar deadbeef
182 let data = [
183 0x01, 0x00, 0x00, 0x07, 0x5a, 0x77, 0x65, 0x69, 0x4c, 0x45, 0x44, 0x00, 0x01, 0x31,
184 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef,
185 ];
186 let mut cursor = Cursor::new(&data[..]);
187 let entry = read_entry(&mut cursor).unwrap();
188 assert_eq!(
189 entry,
190 Some(AuthEntry {
191 family: Family::LOCAL,
192 address: b"ZweiLED".to_vec(),
193 number: b"1".to_vec(),
194 name: b"bar".to_vec(),
195 data: u32::to_be_bytes(0xdead_beef).to_vec(),
196 })
197 );
198 }
199
200 #[test]
201 fn test_read_iterate() {
202 // Data generated via:
203 // xauth -f /tmp/file add :1 bar deadbeef
204 // xauth -f /tmp/file add 1.2.3.4:2 baz aabbccdd
205 let data = [
206 0x01, 0x00, 0x00, 0x07, 0x5a, 0x77, 0x65, 0x69, 0x4c, 0x45, 0x44, 0x00, 0x01, 0x31,
207 0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00,
208 0x04, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, 0x32, 0x00, 0x03, 0x62, 0x61, 0x7a, 0x00,
209 0x04, 0xaa, 0xbb, 0xcc, 0xdd,
210 ];
211 let mut cursor = Cursor::new(&data[..]);
212 for expected in &[
213 AuthEntry {
214 family: Family::LOCAL,
215 address: b"ZweiLED".to_vec(),
216 number: b"1".to_vec(),
217 name: b"bar".to_vec(),
218 data: u32::to_be_bytes(0xdead_beef).to_vec(),
219 },
220 AuthEntry {
221 family: Family::INTERNET,
222 address: vec![1, 2, 3, 4],
223 number: b"2".to_vec(),
224 name: b"baz".to_vec(),
225 data: u32::to_be_bytes(0xaabb_ccdd).to_vec(),
226 },
227 ] {
228 let entry = read_entry(&mut cursor).unwrap();
229 assert_eq!(entry.as_ref(), Some(expected));
230 }
231 let entry = read_entry(&mut cursor).unwrap();
232 assert_eq!(entry, None);
233 }
234 }
235}
236
237pub(crate) type AuthInfo = (Vec<u8>, Vec<u8>);
238
239/// Get the authentication information necessary for connecting to the given display.
240///
241/// - `family` is the protocol family that is used for connecting; this describes how to interpret
242/// the `address`.
243/// - `address` is the raw bytes describing the address that is being connected to.
244/// - `display` is the display number.
245///
246/// If successful, this function returns that can be written to the X11 server as authorization
247/// protocol name and data, respectively.
248pub fn get_auth(family: Family, address: &[u8], display: u16) -> Result<Option<AuthInfo>, Error> {
249 match file::XAuthorityEntries::new()? {
250 None => Ok(None),
251 Some(entries: XAuthorityEntries) => get_auth_impl(entries, family, address, display),
252 }
253}
254
255fn get_auth_impl(
256 entries: impl Iterator<Item = Result<AuthEntry, Error>>,
257 family: Family,
258 address: &[u8],
259 display: u16,
260) -> Result<Option<AuthInfo>, Error> {
261 fn address_matches(
262 (family1, address1): (Family, &[u8]),
263 (family2, address2): (Family, &[u8]),
264 ) -> bool {
265 if family1 == Family::WILD || family2 == Family::WILD {
266 true
267 } else if family1 != family2 {
268 false
269 } else {
270 address1 == address2
271 }
272 }
273
274 fn display_number_matches(entry_number: &[u8], display_number: &[u8]) -> bool {
275 debug_assert!(!display_number.is_empty()); // This case is not handled here and would be a match
276 entry_number.is_empty() || entry_number == display_number
277 }
278
279 let display = display.to_string();
280 let display = display.as_bytes();
281
282 for entry in entries {
283 let entry = entry?;
284
285 if address_matches((family, address), (entry.family, &entry.address))
286 && display_number_matches(&entry.number, display)
287 && entry.name == MIT_MAGIC_COOKIE_1
288 {
289 return Ok(Some((entry.name, entry.data)));
290 }
291 }
292 Ok(None)
293}
294
295#[cfg(test)]
296mod test {
297 use super::{get_auth_impl, AuthEntry, Family, MIT_MAGIC_COOKIE_1};
298 use alloc::vec;
299
300 // Call the given function on a matching auth entry. The function can change the entry.
301 // Afterwards, it should still be a match.
302 fn expect_match<F>(f: F)
303 where
304 F: FnOnce(&mut AuthEntry),
305 {
306 let mut entry = AuthEntry {
307 family: Family::LOCAL,
308 address: b"whatever".to_vec(),
309 number: b"42".to_vec(),
310 name: MIT_MAGIC_COOKIE_1.to_vec(),
311 data: b"1234".to_vec(),
312 };
313 f(&mut entry);
314 let entries = vec![Ok(entry)];
315 assert_eq!(
316 get_auth_impl(entries.into_iter(), Family::LOCAL, b"whatever", 42)
317 .unwrap()
318 .unwrap(),
319 (MIT_MAGIC_COOKIE_1.to_vec(), b"1234".to_vec())
320 );
321 }
322
323 // Call the given function on a matching auth entry. The function can change the entry.
324 // Afterwards, it should no longer match.
325 fn expect_mismatch<F>(f: F)
326 where
327 F: FnOnce(&mut AuthEntry),
328 {
329 let mut entry = AuthEntry {
330 family: Family::LOCAL,
331 address: b"whatever".to_vec(),
332 number: b"42".to_vec(),
333 name: MIT_MAGIC_COOKIE_1.to_vec(),
334 data: b"1234".to_vec(),
335 };
336 f(&mut entry);
337 let entries = vec![Ok(entry)];
338 assert_eq!(
339 get_auth_impl(entries.into_iter(), Family::LOCAL, b"whatever", 42).unwrap(),
340 None
341 );
342 }
343
344 #[test]
345 fn direct_match() {
346 // This checks that an auth entry where all members match, really matches
347 expect_match(|_| {});
348 }
349
350 #[test]
351 fn display_wildcard() {
352 expect_match(|entry| entry.number = vec![]);
353 }
354
355 #[test]
356 fn address_wildcard_match1() {
357 expect_match(|entry| entry.family = Family::WILD);
358 }
359
360 #[test]
361 fn address_wildcard_match2() {
362 let entry = AuthEntry {
363 family: Family::LOCAL,
364 address: b"whatever".to_vec(),
365 number: b"42".to_vec(),
366 name: MIT_MAGIC_COOKIE_1.to_vec(),
367 data: b"1234".to_vec(),
368 };
369 let entries = vec![Ok(entry)];
370 assert_eq!(
371 get_auth_impl(entries.into_iter(), Family::WILD, &[], 42)
372 .unwrap()
373 .unwrap(),
374 (MIT_MAGIC_COOKIE_1.to_vec(), b"1234".to_vec())
375 );
376 }
377
378 #[test]
379 fn family_mismatch() {
380 expect_mismatch(|entry| entry.family = Family::KRB5_PRINCIPAL);
381 }
382
383 #[test]
384 fn address_mismatch() {
385 expect_mismatch(|entry| entry.address = b"something else".to_vec());
386 }
387
388 #[test]
389 fn number_mismatch() {
390 expect_mismatch(|entry| entry.number = b"1337".to_vec());
391 }
392
393 #[test]
394 fn protocol_mismatch() {
395 expect_mismatch(|entry| entry.name = b"XDM-AUTHORIZATION-1".to_vec());
396 }
397}
398