use alloc::string::{String, ToString};
use alloc::vec::Vec;
use xcb_rust_protocol::proto::xproto::FamilyEnum as X11Family;
use xcb_rust_protocol::XcbEnv;
const MIT_MAGIC_COOKIE_1: &[u8] = b"MIT-MAGIC-COOKIE-1";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Family(u16);
impl Family {
pub const INTERNET: Self = Self(0);
pub const DEC_NET: Self = Self(1);
pub const CHAOS: Self = Self(2);
pub const SERVER_INTERPRETED: Self = Self(5);
pub const INTERNET6: Self = Self(6);
pub const WILD: Self = Self(65535);
pub const LOCAL: Self = Self(256);
pub const NETNAME: Self = Self(254);
pub const KRB5_PRINCIPAL: Self = Self(253);
pub const LOCAL_HOST: Self = Self(252);
}
impl From<X11Family> for Family {
fn from(value: X11Family) -> Self {
Self(value.0.into())
}
}
impl From<u16> for Family {
fn from(value: u16) -> Self {
Self(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct AuthEntry {
family: Family,
address: Vec<u8>,
number: Vec<u8>,
name: Vec<u8>,
data: Vec<u8>,
}
mod file {
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use rusl::string::unix_str::UnixStr;
use super::AuthEntry;
#[inline]
fn read_u16(read: &[u8], offset: usize) -> Option<u16> {
Some(u16::from_be_bytes(
read.get(offset..offset + 2)?.try_into().unwrap(),
))
}
fn read_string(read: &[u8], offset: usize) -> Option<(Vec<u8>, usize)> {
let length = read_u16(read, offset)? as usize;
Some((
read.get(offset + 2..offset + 2 + length)?.to_vec(),
offset + 2 + length,
))
}
fn read_entry(read: &[u8], base_offset: &mut usize) -> Option<AuthEntry> {
let family = read_u16(read, *base_offset)?.into();
*base_offset += 2;
let (address, offset) = read_string(read, *base_offset)?;
let (number, offset) = read_string(read, offset)?;
let (name, offset) = read_string(read, offset)?;
let (data, offset) = read_string(read, offset)?;
*base_offset = offset;
Some(AuthEntry {
family,
address,
number,
name,
data,
})
}
#[derive(Debug)]
pub(crate) struct XAuthorityEntries(Vec<u8>, usize);
impl XAuthorityEntries {
pub(crate) fn new(
auth_file: &UnixStr,
) -> Result<Option<XAuthorityEntries>, tiny_std::Error> {
tiny_std::fs::read(auth_file)
.ok()
.map(|buf| Ok(XAuthorityEntries(buf, 0)))
.transpose()
}
}
impl Iterator for XAuthorityEntries {
type Item = AuthEntry;
fn next(&mut self) -> Option<Self::Item> {
read_entry(&self.0, &mut self.1)
}
}
#[cfg(test)]
mod test {
use alloc::vec;
use std::io::Cursor;
use super::super::{AuthEntry, Family};
use super::read_entry;
#[test]
fn test_read() {
let data = [
0x01, 0x00, 0x00, 0x07, 0x5a, 0x77, 0x65, 0x69, 0x4c, 0x45, 0x44, 0x00, 0x01, 0x31,
0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef,
];
let mut buf = data.to_vec();
let entry = read_entry(&buf, &mut 0).unwrap();
assert_eq!(
entry,
AuthEntry {
family: Family::LOCAL,
address: b"ZweiLED".to_vec(),
number: b"1".to_vec(),
name: b"bar".to_vec(),
data: u32::to_be_bytes(0xdead_beef).to_vec(),
}
);
}
#[test]
fn test_read_iterate() {
let data = [
0x01, 0x00, 0x00, 0x07, 0x5a, 0x77, 0x65, 0x69, 0x4c, 0x45, 0x44, 0x00, 0x01, 0x31,
0x00, 0x03, 0x62, 0x61, 0x72, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00,
0x04, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, 0x32, 0x00, 0x03, 0x62, 0x61, 0x7a, 0x00,
0x04, 0xaa, 0xbb, 0xcc, 0xdd,
];
let mut buf = data.to_vec();
let mut offset = 0;
for expected in &[
AuthEntry {
family: Family::LOCAL,
address: b"ZweiLED".to_vec(),
number: b"1".to_vec(),
name: b"bar".to_vec(),
data: u32::to_be_bytes(0xdead_beef).to_vec(),
},
AuthEntry {
family: Family::INTERNET,
address: vec![1, 2, 3, 4],
number: b"2".to_vec(),
name: b"baz".to_vec(),
data: u32::to_be_bytes(0xaabb_ccdd).to_vec(),
},
] {
let entry = read_entry(&buf, &mut offset).unwrap();
assert_eq!(&entry, expected);
}
let entry = read_entry(&buf, &mut offset);
assert_eq!(entry, None);
}
}
}
pub(crate) type AuthInfo = (Vec<u8>, Vec<u8>);
pub fn get_auth(
xcb_env: XcbEnv,
family: Family,
address: &[u8],
display: u16,
) -> Result<Option<AuthInfo>, tiny_std::Error> {
if let Some(xauth_file) = xcb_env.x_authority {
return match file::XAuthorityEntries::new(xauth_file)? {
None => Ok(None),
Some(entries) => Ok(get_auth_impl(entries, family, address, display)),
};
}
Ok(None)
}
fn get_auth_impl(
entries: impl Iterator<Item = AuthEntry>,
family: Family,
address: &[u8],
display: u16,
) -> Option<AuthInfo> {
fn address_matches(
(family1, address1): (Family, &[u8]),
(family2, address2): (Family, &[u8]),
) -> bool {
if family1 == Family::WILD || family2 == Family::WILD {
true
} else if family1 != family2 {
false
} else {
address1 == address2
}
}
fn display_number_matches(entry_number: &[u8], display_number: &[u8]) -> bool {
debug_assert!(!display_number.is_empty()); entry_number.is_empty() || entry_number == display_number
}
let display = display.to_string();
let display = display.as_bytes();
for entry in entries {
if address_matches((family, address), (entry.family, &entry.address))
&& display_number_matches(&entry.number, display)
&& entry.name == MIT_MAGIC_COOKIE_1
{
return Some((entry.name, entry.data));
}
}
None
}
#[cfg(test)]
mod test {
use alloc::vec;
use super::{get_auth_impl, AuthEntry, Family, MIT_MAGIC_COOKIE_1};
fn expect_match<F>(f: F)
where
F: FnOnce(&mut AuthEntry),
{
let mut entry = AuthEntry {
family: Family::LOCAL,
address: b"whatever".to_vec(),
number: b"42".to_vec(),
name: MIT_MAGIC_COOKIE_1.to_vec(),
data: b"1234".to_vec(),
};
f(&mut entry);
let entries = vec![entry];
let res = get_auth_impl(entries.into_iter(), Family::LOCAL, b"whatever", 42).unwrap();
assert_eq!(res, (MIT_MAGIC_COOKIE_1.to_vec(), b"1234".to_vec()));
}
fn expect_mismatch<F>(f: F)
where
F: FnOnce(&mut AuthEntry),
{
let mut entry = AuthEntry {
family: Family::LOCAL,
address: b"whatever".to_vec(),
number: b"42".to_vec(),
name: MIT_MAGIC_COOKIE_1.to_vec(),
data: b"1234".to_vec(),
};
f(&mut entry);
let entries = vec![entry];
assert_eq!(
get_auth_impl(entries.into_iter(), Family::LOCAL, b"whatever", 42),
None
);
}
#[test]
fn direct_match() {
expect_match(|_| {});
}
#[test]
fn display_wildcard() {
expect_match(|entry| entry.number = vec![]);
}
#[test]
fn address_wildcard_match1() {
expect_match(|entry| entry.family = Family::WILD);
}
#[test]
fn address_wildcard_match2() {
let entry = AuthEntry {
family: Family::LOCAL,
address: b"whatever".to_vec(),
number: b"42".to_vec(),
name: MIT_MAGIC_COOKIE_1.to_vec(),
data: b"1234".to_vec(),
};
let entries = vec![entry];
assert_eq!(
get_auth_impl(entries.into_iter(), Family::WILD, &[], 42).unwrap(),
(MIT_MAGIC_COOKIE_1.to_vec(), b"1234".to_vec())
);
}
#[test]
fn family_mismatch() {
expect_mismatch(|entry| entry.family = Family::KRB5_PRINCIPAL);
}
#[test]
fn address_mismatch() {
expect_mismatch(|entry| entry.address = b"something else".to_vec());
}
#[test]
fn number_mismatch() {
expect_mismatch(|entry| entry.number = b"1337".to_vec());
}
#[test]
fn protocol_mismatch() {
expect_mismatch(|entry| entry.name = b"XDM-AUTHORIZATION-1".to_vec());
}
}