1use super::*;
3
4pub fn default_auth_state_dir() -> PathBuf {
5 std::env::var_os("PB_MAPPER_AUTH_STATE_DIR")
6 .map(PathBuf::from)
7 .unwrap_or_else(platform_default_auth_state_dir)
8}
9
10pub(crate) fn platform_default_auth_state_dir() -> PathBuf {
14 #[cfg(windows)]
15 {
16 let base = std::env::var_os("LOCALAPPDATA")
17 .or_else(|| std::env::var_os("APPDATA"))
18 .map(PathBuf::from)
19 .unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"));
20 base.join("pb-mapper").join("auth")
21 }
22 #[cfg(target_os = "macos")]
23 {
24 match std::env::var_os("HOME") {
25 Some(home) => PathBuf::from(home)
26 .join("Library")
27 .join("Application Support")
28 .join("pb-mapper")
29 .join("auth"),
30 None => PathBuf::from("/Library/Application Support/pb-mapper/auth"),
31 }
32 }
33 #[cfg(not(any(windows, target_os = "macos")))]
34 {
35 linux_default_auth_state_dir(
36 unix_effective_uid(),
37 linux_system_auth_dir_usable(),
38 std::env::var_os("XDG_DATA_HOME").as_deref(),
39 std::env::var_os("HOME").as_deref(),
40 )
41 }
42}
43
44#[cfg(not(any(windows, target_os = "macos")))]
45pub(crate) fn linux_default_auth_state_dir(
46 euid: u32,
47 system_dir_usable: bool,
48 xdg_data_home: Option<&std::ffi::OsStr>,
49 home: Option<&std::ffi::OsStr>,
50) -> PathBuf {
51 if euid == 0 || system_dir_usable {
52 return PathBuf::from(DEFAULT_AUTH_STATE_DIR);
53 }
54 if let Some(xdg) = xdg_data_home
55 && !xdg.is_empty()
56 {
57 return PathBuf::from(xdg).join("pb-mapper").join("auth");
58 }
59 if let Some(home) = home
60 && !home.is_empty()
61 {
62 return PathBuf::from(home)
63 .join(".local")
64 .join("share")
65 .join("pb-mapper")
66 .join("auth");
67 }
68 PathBuf::from(DEFAULT_AUTH_STATE_DIR)
69}
70
71#[cfg(not(any(windows, target_os = "macos")))]
72pub(super) fn unix_effective_uid() -> u32 {
73 unsafe extern "C" {
74 fn geteuid() -> u32;
75 }
76 unsafe { geteuid() }
77}
78
79#[cfg(not(any(windows, target_os = "macos")))]
80pub(super) fn linux_system_auth_dir_usable() -> bool {
81 let path = Path::new(DEFAULT_AUTH_STATE_DIR);
82 path.is_dir() && unix_path_is_writable(path)
83}
84
85#[cfg(not(any(windows, target_os = "macos")))]
86fn unix_path_is_writable(path: &Path) -> bool {
87 use std::os::unix::ffi::OsStrExt;
88 let Ok(c_path) = std::ffi::CString::new(path.as_os_str().as_bytes()) else {
89 return false;
90 };
91 unsafe extern "C" {
92 fn access(pathname: *const std::os::raw::c_char, mode: i32) -> i32;
93 }
94 const W_OK: i32 = 2;
95 unsafe { access(c_path.as_ptr(), W_OK) == 0 }
96}
97
98impl Default for AuthConfig {
99 fn default() -> Self {
100 Self {
101 state_dir: default_auth_state_dir(),
102 max_temporary_keys: env_usize(
103 "PB_MAPPER_AUTH_MAX_TEMP_KEYS",
104 DEFAULT_TEMP_KEY_CAPACITY,
105 1,
106 MAX_TEMP_KEY_CAPACITY,
107 ),
108 max_temporary_key_ttl: Duration::from_secs(env_u64(
109 "PB_MAPPER_AUTH_MAX_TEMP_TTL_SECS",
110 DEFAULT_MAX_TEMP_KEY_TTL.as_secs(),
111 MIN_TEMP_KEY_TTL.as_secs(),
112 MAX_TEMP_KEY_TTL.as_secs(),
113 )),
114 legacy_protocol: legacy_protocol_from_env(),
115 }
116 }
117}
118
119fn legacy_protocol_from_env() -> LegacyProtocolPolicy {
120 match std::env::var("PB_MAPPER_LEGACY_PROTOCOL") {
121 Err(std::env::VarError::NotPresent) => LegacyProtocolPolicy::Allow,
122 Err(std::env::VarError::NotUnicode(_)) => {
123 tracing::error!(
124 event = "legacy_protocol_config_invalid",
125 "PB_MAPPER_LEGACY_PROTOCOL is not UTF-8; denying legacy framing"
126 );
127 LegacyProtocolPolicy::Deny
128 }
129 Ok(value) => parse_legacy_protocol_policy(&value).unwrap_or_else(|| {
130 tracing::error!(
131 event = "legacy_protocol_config_invalid",
132 value,
133 "PB_MAPPER_LEGACY_PROTOCOL must be `allow` or `deny`; denying legacy framing"
134 );
135 LegacyProtocolPolicy::Deny
136 }),
137 }
138}
139
140pub(super) fn parse_legacy_protocol_policy(value: &str) -> Option<LegacyProtocolPolicy> {
141 match value.trim().to_ascii_lowercase().as_str() {
142 "allow" => Some(LegacyProtocolPolicy::Allow),
143 "deny" => Some(LegacyProtocolPolicy::Deny),
144 _ => None,
145 }
146}
147
148fn env_usize(name: &str, default: usize, min: usize, max: usize) -> usize {
149 env_bounded(name, default, min, max)
150}
151
152fn env_u64(name: &str, default: u64, min: u64, max: u64) -> u64 {
153 env_bounded(name, default, min, max)
154}
155
156fn env_bounded<T>(name: &str, default: T, min: T, max: T) -> T
157where
158 T: std::str::FromStr + PartialOrd + Copy + fmt::Display,
159{
160 match std::env::var(name) {
161 Err(std::env::VarError::NotPresent) => default,
162 Ok(raw) => match raw.parse::<T>() {
163 Ok(value) if value >= min && value <= max => value,
164 _ => {
165 tracing::warn!(
166 event = "auth_config_value_invalid",
167 variable = name,
168 value = raw,
169 min = %min,
170 max = %max,
171 fallback = %default,
172 "invalid authentication configuration value; using the default"
173 );
174 default
175 }
176 },
177 Err(std::env::VarError::NotUnicode(_)) => {
178 tracing::warn!(
179 event = "auth_config_value_invalid",
180 variable = name,
181 min = %min,
182 max = %max,
183 fallback = %default,
184 "authentication configuration value is not UTF-8; using the default"
185 );
186 default
187 }
188 }
189}