x11rb_protocol/
xauth.rs

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) => 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}