]>
Commit | Line | Data |
---|---|---|
68ccdf09 DM |
1 | use std::collections::HashMap; |
2 | use std::sync::{Arc, RwLock}; | |
3 | ||
f7d4e4b5 | 4 | use anyhow::{bail, Error}; |
579728c6 | 5 | use lazy_static::lazy_static; |
579728c6 | 6 | |
6ef1b649 WB |
7 | use proxmox_schema::*; |
8 | use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | |
579728c6 | 9 | |
b65dfff5 DM |
10 | use pbs_api_types::{ |
11 | Authid, Userid, ApiToken, User, | |
2b7f8dd5 WB |
12 | }; |
13 | ||
cb80ffc1 | 14 | use crate::ConfigVersionCache; |
ba3d7e19 DM |
15 | |
16 | use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | |
579728c6 DM |
17 | |
18 | lazy_static! { | |
2322a980 | 19 | pub static ref CONFIG: SectionConfig = init(); |
579728c6 DM |
20 | } |
21 | ||
579728c6 | 22 | fn init() -> SectionConfig { |
e6dc35ac FG |
23 | let mut config = SectionConfig::new(&Authid::API_SCHEMA); |
24 | ||
25 | let user_schema = match User::API_SCHEMA { | |
26 | Schema::Object(ref user_schema) => user_schema, | |
579728c6 DM |
27 | _ => unreachable!(), |
28 | }; | |
e6dc35ac FG |
29 | let user_plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), user_schema); |
30 | config.register_plugin(user_plugin); | |
579728c6 | 31 | |
e6dc35ac FG |
32 | let token_schema = match ApiToken::API_SCHEMA { |
33 | Schema::Object(ref token_schema) => token_schema, | |
34 | _ => unreachable!(), | |
35 | }; | |
36 | let token_plugin = SectionConfigPlugin::new("token".to_string(), Some("tokenid".to_string()), token_schema); | |
37 | config.register_plugin(token_plugin); | |
579728c6 DM |
38 | |
39 | config | |
40 | } | |
41 | ||
42 | pub const USER_CFG_FILENAME: &str = "/etc/proxmox-backup/user.cfg"; | |
43 | pub const USER_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.user.lck"; | |
44 | ||
b65dfff5 DM |
45 | /// Get exclusive lock |
46 | pub fn lock_config() -> Result<BackupLockGuard, Error> { | |
47 | open_backup_lockfile(USER_CFG_LOCKFILE, None, true) | |
48 | } | |
49 | ||
579728c6 | 50 | pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { |
3eeba687 | 51 | |
25877d05 | 52 | let content = proxmox_sys::fs::file_read_optional_string(USER_CFG_FILENAME)? |
e062ebbc | 53 | .unwrap_or_else(|| "".to_string()); |
579728c6 DM |
54 | |
55 | let digest = openssl::sha::sha256(content.as_bytes()); | |
56 | let mut data = CONFIG.parse(USER_CFG_FILENAME, &content)?; | |
57 | ||
58 | if data.sections.get("root@pam").is_none() { | |
9c5c383b | 59 | let user: User = User { |
e7cb4dc5 | 60 | userid: Userid::root_userid().clone(), |
9c5c383b DC |
61 | comment: Some("Superuser".to_string()), |
62 | enable: None, | |
63 | expire: None, | |
64 | firstname: None, | |
65 | lastname: None, | |
66 | email: None, | |
67 | }; | |
579728c6 DM |
68 | data.set_data("root@pam", "user", &user).unwrap(); |
69 | } | |
70 | ||
71 | Ok((data, digest)) | |
72 | } | |
73 | ||
109d7817 | 74 | pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> { |
68ccdf09 DM |
75 | |
76 | struct ConfigCache { | |
109d7817 | 77 | data: Option<Arc<SectionConfigData>>, |
68ccdf09 DM |
78 | last_mtime: i64, |
79 | last_mtime_nsec: i64, | |
80 | } | |
81 | ||
82 | lazy_static! { | |
83 | static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new( | |
84 | ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 }); | |
85 | } | |
86 | ||
b9f2f761 DM |
87 | let stat = match nix::sys::stat::stat(USER_CFG_FILENAME) { |
88 | Ok(stat) => Some(stat), | |
89 | Err(nix::Error::Sys(nix::errno::Errno::ENOENT)) => None, | |
90 | Err(err) => bail!("unable to stat '{}' - {}", USER_CFG_FILENAME, err), | |
91 | }; | |
68ccdf09 | 92 | |
bd88dc41 | 93 | { // limit scope |
68ccdf09 | 94 | let cache = CACHED_CONFIG.read().unwrap(); |
bd88dc41 DM |
95 | if let Some(ref config) = cache.data { |
96 | if let Some(stat) = stat { | |
97 | if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec { | |
98 | return Ok(config.clone()); | |
99 | } | |
100 | } else if cache.last_mtime == 0 && cache.last_mtime_nsec == 0 { | |
109d7817 | 101 | return Ok(config.clone()); |
68ccdf09 DM |
102 | } |
103 | } | |
104 | } | |
105 | ||
109d7817 | 106 | let (config, _digest) = config()?; |
68ccdf09 DM |
107 | let config = Arc::new(config); |
108 | ||
109 | let mut cache = CACHED_CONFIG.write().unwrap(); | |
b9f2f761 DM |
110 | if let Some(stat) = stat { |
111 | cache.last_mtime = stat.st_mtime; | |
112 | cache.last_mtime_nsec = stat.st_mtime_nsec; | |
113 | } | |
109d7817 | 114 | cache.data = Some(config.clone()); |
68ccdf09 | 115 | |
109d7817 | 116 | Ok(config) |
68ccdf09 DM |
117 | } |
118 | ||
579728c6 | 119 | pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { |
9a37bd6c | 120 | let raw = CONFIG.write(USER_CFG_FILENAME, config)?; |
b65dfff5 | 121 | replace_backup_config(USER_CFG_FILENAME, raw.as_bytes())?; |
579728c6 | 122 | |
cb80ffc1 | 123 | // increase user version |
fda19dcc | 124 | // We use this in CachedUserInfo |
cb80ffc1 DM |
125 | let version_cache = ConfigVersionCache::new()?; |
126 | version_cache.increase_user_cache_generation(); | |
fda19dcc | 127 | |
579728c6 DM |
128 | Ok(()) |
129 | } | |
130 | ||
ba3d7e19 DM |
131 | /// Only exposed for testing |
132 | #[doc(hidden)] | |
133 | pub fn test_cfg_from_str(raw: &str) -> Result<(SectionConfigData, [u8;32]), Error> { | |
18077ac6 FG |
134 | let cfg = init(); |
135 | let parsed = cfg.parse("test_user_cfg", raw)?; | |
136 | ||
137 | Ok((parsed, [0;32])) | |
138 | } | |
139 | ||
579728c6 | 140 | // shell completion helper |
e6dc35ac | 141 | pub fn complete_userid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { |
579728c6 | 142 | match config() { |
e6dc35ac FG |
143 | Ok((data, _digest)) => { |
144 | data.sections.iter() | |
145 | .filter_map(|(id, (section_type, _))| { | |
146 | if section_type == "user" { | |
147 | Some(id.to_string()) | |
148 | } else { | |
149 | None | |
150 | } | |
151 | }).collect() | |
152 | }, | |
579728c6 DM |
153 | Err(_) => return vec![], |
154 | } | |
155 | } | |
e6dc35ac FG |
156 | |
157 | // shell completion helper | |
158 | pub fn complete_authid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | |
159 | match config() { | |
160 | Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), | |
161 | Err(_) => vec![], | |
162 | } | |
163 | } | |
2156dec5 FG |
164 | |
165 | // shell completion helper | |
166 | pub fn complete_token_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> { | |
167 | let data = match config() { | |
168 | Ok((data, _digest)) => data, | |
169 | Err(_) => return Vec::new(), | |
170 | }; | |
171 | ||
172 | match param.get("userid") { | |
173 | Some(userid) => { | |
174 | let user = data.lookup::<User>("user", userid); | |
175 | let tokens = data.convert_to_typed_array("token"); | |
176 | match (user, tokens) { | |
177 | (Ok(_), Ok(tokens)) => { | |
178 | tokens | |
179 | .into_iter() | |
180 | .filter_map(|token: ApiToken| { | |
181 | let tokenid = token.tokenid; | |
182 | if tokenid.is_token() && tokenid.user() == userid { | |
183 | Some(tokenid.tokenname().unwrap().as_str().to_string()) | |
184 | } else { | |
185 | None | |
186 | } | |
187 | }).collect() | |
188 | }, | |
189 | _ => vec![], | |
190 | } | |
191 | }, | |
192 | None => vec![], | |
193 | } | |
194 | } |