xcb_rust_connection/helpers/
xauth.rs

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