1 use std
::collections
::HashMap
;
2 use std
::sync
::{Arc, RwLock}
;
4 use anyhow
::{bail, Error}
;
5 use lazy_static
::lazy_static
;
8 use proxmox_section_config
::{SectionConfig, SectionConfigData, SectionConfigPlugin}
;
11 Authid
, Userid
, ApiToken
, User
,
14 use crate::ConfigVersionCache
;
16 use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}
;
19 pub static ref CONFIG
: SectionConfig
= init();
22 fn init() -> SectionConfig
{
23 let mut config
= SectionConfig
::new(&Authid
::API_SCHEMA
);
25 let user_schema
= match User
::API_SCHEMA
{
26 Schema
::Object(ref user_schema
) => user_schema
,
29 let user_plugin
= SectionConfigPlugin
::new("user".to_string(), Some("userid".to_string()), user_schema
);
30 config
.register_plugin(user_plugin
);
32 let token_schema
= match ApiToken
::API_SCHEMA
{
33 Schema
::Object(ref token_schema
) => token_schema
,
36 let token_plugin
= SectionConfigPlugin
::new("token".to_string(), Some("tokenid".to_string()), token_schema
);
37 config
.register_plugin(token_plugin
);
42 pub const USER_CFG_FILENAME
: &str = "/etc/proxmox-backup/user.cfg";
43 pub const USER_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.user.lck";
45 /// Get exclusive lock
46 pub fn lock_config() -> Result
<BackupLockGuard
, Error
> {
47 open_backup_lockfile(USER_CFG_LOCKFILE
, None
, true)
50 pub fn config() -> Result
<(SectionConfigData
, [u8;32]), Error
> {
52 let content
= proxmox_sys
::fs
::file_read_optional_string(USER_CFG_FILENAME
)?
53 .unwrap_or_else(|| "".to_string());
55 let digest
= openssl
::sha
::sha256(content
.as_bytes());
56 let mut data
= CONFIG
.parse(USER_CFG_FILENAME
, &content
)?
;
58 if data
.sections
.get("root@pam").is_none() {
59 let user
: User
= User
{
60 userid
: Userid
::root_userid().clone(),
61 comment
: Some("Superuser".to_string()),
68 data
.set_data("root@pam", "user", &user
).unwrap();
74 pub fn cached_config() -> Result
<Arc
<SectionConfigData
>, Error
> {
77 data
: Option
<Arc
<SectionConfigData
>>,
83 static ref CACHED_CONFIG
: RwLock
<ConfigCache
> = RwLock
::new(
84 ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 }
);
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
),
94 let cache
= CACHED_CONFIG
.read().unwrap();
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());
100 } else if cache
.last_mtime
== 0 && cache
.last_mtime_nsec
== 0 {
101 return Ok(config
.clone());
106 let (config
, _digest
) = config()?
;
107 let config
= Arc
::new(config
);
109 let mut cache
= CACHED_CONFIG
.write().unwrap();
110 if let Some(stat
) = stat
{
111 cache
.last_mtime
= stat
.st_mtime
;
112 cache
.last_mtime_nsec
= stat
.st_mtime_nsec
;
114 cache
.data
= Some(config
.clone());
119 pub fn save_config(config
: &SectionConfigData
) -> Result
<(), Error
> {
120 let raw
= CONFIG
.write(USER_CFG_FILENAME
, &config
)?
;
121 replace_backup_config(USER_CFG_FILENAME
, raw
.as_bytes())?
;
123 // increase user version
124 // We use this in CachedUserInfo
125 let version_cache
= ConfigVersionCache
::new()?
;
126 version_cache
.increase_user_cache_generation();
131 /// Only exposed for testing
133 pub fn test_cfg_from_str(raw
: &str) -> Result
<(SectionConfigData
, [u8;32]), Error
> {
135 let parsed
= cfg
.parse("test_user_cfg", raw
)?
;
140 // shell completion helper
141 pub fn complete_userid(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
143 Ok((data
, _digest
)) => {
145 .filter_map(|(id
, (section_type
, _
))| {
146 if section_type
== "user" {
153 Err(_
) => return vec
![],
157 // shell completion helper
158 pub fn complete_authid(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
160 Ok((data
, _digest
)) => data
.sections
.iter().map(|(id
, _
)| id
.to_string()).collect(),
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(),
172 match param
.get("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
)) => {
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())