1 use std
::collections
::HashMap
;
2 use std
::sync
::{Arc, RwLock}
;
4 use anyhow
::{bail, Error}
;
5 use lazy_static
::lazy_static
;
6 use serde
::{Serialize, Deserialize}
;
18 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
20 use crate::api2
::types
::*;
23 static ref CONFIG
: SectionConfig
= init();
26 pub const ENABLE_USER_SCHEMA
: Schema
= BooleanSchema
::new(
27 "Enable the account (default). You can set this to '0' to disable the account.")
31 pub const EXPIRE_USER_SCHEMA
: Schema
= IntegerSchema
::new(
32 "Account expiration date (seconds since epoch). '0' means no expiration date.")
37 pub const FIRST_NAME_SCHEMA
: Schema
= StringSchema
::new("First name.")
38 .format(&SINGLE_LINE_COMMENT_FORMAT
)
43 pub const LAST_NAME_SCHEMA
: Schema
= StringSchema
::new("Last name.")
44 .format(&SINGLE_LINE_COMMENT_FORMAT
)
49 pub const EMAIL_SCHEMA
: Schema
= StringSchema
::new("E-Mail Address.")
50 .format(&SINGLE_LINE_COMMENT_FORMAT
)
58 schema
: PROXMOX_TOKEN_ID_SCHEMA
,
62 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
66 schema
: ENABLE_USER_SCHEMA
,
70 schema
: EXPIRE_USER_SCHEMA
,
74 #[derive(Serialize,Deserialize)]
75 /// ApiToken properties.
78 #[serde(skip_serializing_if="Option::is_none")]
79 pub comment
: Option
<String
>,
80 #[serde(skip_serializing_if="Option::is_none")]
81 pub enable
: Option
<bool
>,
82 #[serde(skip_serializing_if="Option::is_none")]
83 pub expire
: Option
<i64>,
93 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
97 schema
: ENABLE_USER_SCHEMA
,
101 schema
: EXPIRE_USER_SCHEMA
,
105 schema
: FIRST_NAME_SCHEMA
,
108 schema
: LAST_NAME_SCHEMA
,
112 schema
: EMAIL_SCHEMA
,
117 #[derive(Serialize,Deserialize)]
121 #[serde(skip_serializing_if="Option::is_none")]
122 pub comment
: Option
<String
>,
123 #[serde(skip_serializing_if="Option::is_none")]
124 pub enable
: Option
<bool
>,
125 #[serde(skip_serializing_if="Option::is_none")]
126 pub expire
: Option
<i64>,
127 #[serde(skip_serializing_if="Option::is_none")]
128 pub firstname
: Option
<String
>,
129 #[serde(skip_serializing_if="Option::is_none")]
130 pub lastname
: Option
<String
>,
131 #[serde(skip_serializing_if="Option::is_none")]
132 pub email
: Option
<String
>,
135 fn init() -> SectionConfig
{
136 let mut config
= SectionConfig
::new(&Authid
::API_SCHEMA
);
138 let user_schema
= match User
::API_SCHEMA
{
139 Schema
::Object(ref user_schema
) => user_schema
,
142 let user_plugin
= SectionConfigPlugin
::new("user".to_string(), Some("userid".to_string()), user_schema
);
143 config
.register_plugin(user_plugin
);
145 let token_schema
= match ApiToken
::API_SCHEMA
{
146 Schema
::Object(ref token_schema
) => token_schema
,
149 let token_plugin
= SectionConfigPlugin
::new("token".to_string(), Some("tokenid".to_string()), token_schema
);
150 config
.register_plugin(token_plugin
);
155 pub const USER_CFG_FILENAME
: &str = "/etc/proxmox-backup/user.cfg";
156 pub const USER_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.user.lck";
158 pub fn config() -> Result
<(SectionConfigData
, [u8;32]), Error
> {
160 let content
= proxmox
::tools
::fs
::file_read_optional_string(USER_CFG_FILENAME
)?
;
161 let content
= content
.unwrap_or(String
::from(""));
163 let digest
= openssl
::sha
::sha256(content
.as_bytes());
164 let mut data
= CONFIG
.parse(USER_CFG_FILENAME
, &content
)?
;
166 if data
.sections
.get("root@pam").is_none() {
167 let user
: User
= User
{
168 userid
: Userid
::root_userid().clone(),
169 comment
: Some("Superuser".to_string()),
176 data
.set_data("root@pam", "user", &user
).unwrap();
182 pub fn cached_config() -> Result
<Arc
<SectionConfigData
>, Error
> {
185 data
: Option
<Arc
<SectionConfigData
>>,
187 last_mtime_nsec
: i64,
191 static ref CACHED_CONFIG
: RwLock
<ConfigCache
> = RwLock
::new(
192 ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 }
);
195 let stat
= match nix
::sys
::stat
::stat(USER_CFG_FILENAME
) {
196 Ok(stat
) => Some(stat
),
197 Err(nix
::Error
::Sys(nix
::errno
::Errno
::ENOENT
)) => None
,
198 Err(err
) => bail
!("unable to stat '{}' - {}", USER_CFG_FILENAME
, err
),
202 let cache
= CACHED_CONFIG
.read().unwrap();
203 if let Some(ref config
) = cache
.data
{
204 if let Some(stat
) = stat
{
205 if stat
.st_mtime
== cache
.last_mtime
&& stat
.st_mtime_nsec
== cache
.last_mtime_nsec
{
206 return Ok(config
.clone());
208 } else if cache
.last_mtime
== 0 && cache
.last_mtime_nsec
== 0 {
209 return Ok(config
.clone());
214 let (config
, _digest
) = config()?
;
215 let config
= Arc
::new(config
);
217 let mut cache
= CACHED_CONFIG
.write().unwrap();
218 if let Some(stat
) = stat
{
219 cache
.last_mtime
= stat
.st_mtime
;
220 cache
.last_mtime_nsec
= stat
.st_mtime_nsec
;
222 cache
.data
= Some(config
.clone());
227 pub fn save_config(config
: &SectionConfigData
) -> Result
<(), Error
> {
228 let raw
= CONFIG
.write(USER_CFG_FILENAME
, &config
)?
;
230 let backup_user
= crate::backup
::backup_user()?
;
231 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0640);
232 // set the correct owner/group/permissions while saving file
233 // owner(rw) = root, group(r)= backup
234 let options
= CreateOptions
::new()
236 .owner(nix
::unistd
::ROOT
)
237 .group(backup_user
.gid
);
239 replace_file(USER_CFG_FILENAME
, raw
.as_bytes(), options
)?
;
244 // shell completion helper
245 pub fn complete_userid(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
247 Ok((data
, _digest
)) => {
249 .filter_map(|(id
, (section_type
, _
))| {
250 if section_type
== "user" {
257 Err(_
) => return vec
![],
261 // shell completion helper
262 pub fn complete_authid(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
264 Ok((data
, _digest
)) => data
.sections
.iter().map(|(id
, _
)| id
.to_string()).collect(),
269 // shell completion helper
270 pub fn complete_token_name(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
271 let data
= match config() {
272 Ok((data
, _digest
)) => data
,
273 Err(_
) => return Vec
::new(),
276 match param
.get("userid") {
278 let user
= data
.lookup
::<User
>("user", userid
);
279 let tokens
= data
.convert_to_typed_array("token");
280 match (user
, tokens
) {
281 (Ok(_
), Ok(tokens
)) => {
284 .filter_map(|token
: ApiToken
| {
285 let tokenid
= token
.tokenid
;
286 if tokenid
.is_token() && tokenid
.user() == userid
{
287 Some(tokenid
.tokenname().unwrap().as_str().to_string())