1 | use std::{fmt, path::PathBuf}; |
2 | |
3 | use futures_util::StreamExt; |
4 | use tracing::trace; |
5 | use xdg_home::home_dir; |
6 | use zvariant::Str; |
7 | |
8 | use crate::{file::FileLines, Error, Result}; |
9 | |
10 | #[derive (Debug)] |
11 | pub(super) struct Cookie { |
12 | id: usize, |
13 | cookie: String, |
14 | } |
15 | |
16 | impl Cookie { |
17 | #[cfg (feature = "p2p" )] |
18 | pub fn id(&self) -> usize { |
19 | self.id |
20 | } |
21 | |
22 | pub fn cookie(&self) -> &str { |
23 | &self.cookie |
24 | } |
25 | |
26 | fn keyring_path() -> Result<PathBuf> { |
27 | let mut path = home_dir() |
28 | .ok_or_else(|| Error::Handshake("Failed to determine home directory" .into()))?; |
29 | path.push(".dbus-keyrings" ); |
30 | Ok(path) |
31 | } |
32 | |
33 | async fn read_keyring(context: &CookieContext<'_>) -> Result<Vec<Cookie>> { |
34 | let mut path = Cookie::keyring_path()?; |
35 | #[cfg (unix)] |
36 | { |
37 | use std::os::unix::fs::PermissionsExt; |
38 | |
39 | let perms = crate::file::metadata(&path).await?.permissions().mode(); |
40 | if perms & 0o066 != 0 { |
41 | return Err(Error::Handshake( |
42 | "DBus keyring has invalid permissions" .into(), |
43 | )); |
44 | } |
45 | } |
46 | #[cfg (not(unix))] |
47 | { |
48 | // FIXME: add code to check directory permissions |
49 | } |
50 | path.push(&*context.0); |
51 | trace!("Reading keyring {:?}" , path); |
52 | let mut lines = FileLines::open(&path).await?.enumerate(); |
53 | let mut cookies = vec![]; |
54 | while let Some((n, line)) = lines.next().await { |
55 | let line = line?; |
56 | let mut split = line.split_whitespace(); |
57 | let id = split |
58 | .next() |
59 | .ok_or_else(|| { |
60 | Error::Handshake(format!( |
61 | "DBus cookie ` {}` missing ID at line {n}" , |
62 | path.display(), |
63 | )) |
64 | })? |
65 | .parse() |
66 | .map_err(|e| { |
67 | Error::Handshake(format!( |
68 | "Failed to parse cookie ID in file ` {}` at line {n}: {e}" , |
69 | path.display(), |
70 | )) |
71 | })?; |
72 | let _ = split.next().ok_or_else(|| { |
73 | Error::Handshake(format!( |
74 | "DBus cookie ` {}` missing creation time at line {n}" , |
75 | path.display(), |
76 | )) |
77 | })?; |
78 | let cookie = split |
79 | .next() |
80 | .ok_or_else(|| { |
81 | Error::Handshake(format!( |
82 | "DBus cookie ` {}` missing cookie data at line {}" , |
83 | path.to_str().unwrap(), |
84 | n |
85 | )) |
86 | })? |
87 | .to_string(); |
88 | cookies.push(Cookie { id, cookie }) |
89 | } |
90 | trace!("Loaded keyring {:?}" , cookies); |
91 | Ok(cookies) |
92 | } |
93 | |
94 | pub async fn lookup(context: &CookieContext<'_>, id: usize) -> Result<Cookie> { |
95 | let keyring = Self::read_keyring(context).await?; |
96 | keyring |
97 | .into_iter() |
98 | .find(|c| c.id == id) |
99 | .ok_or_else(|| Error::Handshake(format!("DBus cookie ID {id} not found" ))) |
100 | } |
101 | |
102 | #[cfg (feature = "p2p" )] |
103 | pub async fn first(context: &CookieContext<'_>) -> Result<Cookie> { |
104 | let keyring = Self::read_keyring(context).await?; |
105 | keyring |
106 | .into_iter() |
107 | .next() |
108 | .ok_or_else(|| Error::Handshake("No cookies available" .into())) |
109 | } |
110 | } |
111 | |
112 | #[derive (Debug)] |
113 | pub struct CookieContext<'c>(Str<'c>); |
114 | |
115 | impl<'c> TryFrom<Str<'c>> for CookieContext<'c> { |
116 | type Error = Error; |
117 | |
118 | fn try_from(value: Str<'c>) -> Result<Self> { |
119 | if value.is_empty() { |
120 | return Err(Error::Handshake("Empty cookie context" .into())); |
121 | } else if !value.is_ascii() || value.contains(['/' , ' \\' , ' ' , ' \n' , ' \r' , ' \t' , '.' ]) { |
122 | return Err(Error::Handshake( |
123 | "Invalid characters in cookie context" .into(), |
124 | )); |
125 | } |
126 | |
127 | Ok(Self(value)) |
128 | } |
129 | } |
130 | |
131 | impl fmt::Display for CookieContext<'_> { |
132 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
133 | write!(f, " {}" , self.0) |
134 | } |
135 | } |
136 | |
137 | impl Default for CookieContext<'_> { |
138 | fn default() -> Self { |
139 | Self(Str::from_static("org_freedesktop_general" )) |
140 | } |
141 | } |
142 | |
143 | impl From<hex::FromHexError> for Error { |
144 | fn from(e: hex::FromHexError) -> Self { |
145 | Error::Handshake(format!("Invalid hexcode: {e}" )) |
146 | } |
147 | } |
148 | |