Skip to main content

pb_mapper_auth/
config.rs

1//! Authentication configuration and platform state-directory defaults.
2use 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
10/// Linux systemd/Docker keep `/var/lib/pb-mapper/auth` when that path is usable
11/// (root, or an already-writable service directory). Unprivileged Linux,
12/// macOS, and Windows binaries need an application data directory instead.
13pub(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}