1 use std
::collections
::HashMap
;
2 use std
::sync
::{Arc, RwLock}
;
4 use anyhow
::{bail, Error}
;
5 use lazy_static
::lazy_static
;
17 Authid
, Userid
, ApiToken
, User
,
20 use crate::memcom
::Memcom
;
22 use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}
;
25 pub static ref CONFIG
: SectionConfig
= init();
28 fn init() -> SectionConfig
{
29 let mut config
= SectionConfig
::new(&Authid
::API_SCHEMA
);
31 let user_schema
= match User
::API_SCHEMA
{
32 Schema
::Object(ref user_schema
) => user_schema
,
35 let user_plugin
= SectionConfigPlugin
::new("user".to_string(), Some("userid".to_string()), user_schema
);
36 config
.register_plugin(user_plugin
);
38 let token_schema
= match ApiToken
::API_SCHEMA
{
39 Schema
::Object(ref token_schema
) => token_schema
,
42 let token_plugin
= SectionConfigPlugin
::new("token".to_string(), Some("tokenid".to_string()), token_schema
);
43 config
.register_plugin(token_plugin
);
48 pub const USER_CFG_FILENAME
: &str = "/etc/proxmox-backup/user.cfg";
49 pub const USER_CFG_LOCKFILE
: &str = "/etc/proxmox-backup/.user.lck";
51 /// Get exclusive lock
52 pub fn lock_config() -> Result
<BackupLockGuard
, Error
> {
53 open_backup_lockfile(USER_CFG_LOCKFILE
, None
, true)
56 pub fn config() -> Result
<(SectionConfigData
, [u8;32]), Error
> {
58 let content
= proxmox
::tools
::fs
::file_read_optional_string(USER_CFG_FILENAME
)?
59 .unwrap_or_else(|| "".to_string());
61 let digest
= openssl
::sha
::sha256(content
.as_bytes());
62 let mut data
= CONFIG
.parse(USER_CFG_FILENAME
, &content
)?
;
64 if data
.sections
.get("root@pam").is_none() {
65 let user
: User
= User
{
66 userid
: Userid
::root_userid().clone(),
67 comment
: Some("Superuser".to_string()),
74 data
.set_data("root@pam", "user", &user
).unwrap();
80 pub fn cached_config() -> Result
<Arc
<SectionConfigData
>, Error
> {
83 data
: Option
<Arc
<SectionConfigData
>>,
89 static ref CACHED_CONFIG
: RwLock
<ConfigCache
> = RwLock
::new(
90 ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 }
);
93 let stat
= match nix
::sys
::stat
::stat(USER_CFG_FILENAME
) {
94 Ok(stat
) => Some(stat
),
95 Err(nix
::Error
::Sys(nix
::errno
::Errno
::ENOENT
)) => None
,
96 Err(err
) => bail
!("unable to stat '{}' - {}", USER_CFG_FILENAME
, err
),
100 let cache
= CACHED_CONFIG
.read().unwrap();
101 if let Some(ref config
) = cache
.data
{
102 if let Some(stat
) = stat
{
103 if stat
.st_mtime
== cache
.last_mtime
&& stat
.st_mtime_nsec
== cache
.last_mtime_nsec
{
104 return Ok(config
.clone());
106 } else if cache
.last_mtime
== 0 && cache
.last_mtime_nsec
== 0 {
107 return Ok(config
.clone());
112 let (config
, _digest
) = config()?
;
113 let config
= Arc
::new(config
);
115 let mut cache
= CACHED_CONFIG
.write().unwrap();
116 if let Some(stat
) = stat
{
117 cache
.last_mtime
= stat
.st_mtime
;
118 cache
.last_mtime_nsec
= stat
.st_mtime_nsec
;
120 cache
.data
= Some(config
.clone());
125 pub fn save_config(config
: &SectionConfigData
) -> Result
<(), Error
> {
126 let raw
= CONFIG
.write(USER_CFG_FILENAME
, &config
)?
;
127 replace_backup_config(USER_CFG_FILENAME
, raw
.as_bytes())?
;
129 // increase user cache generation
130 // We use this in CachedUserInfo
131 let memcom
= Memcom
::new()?
;
132 memcom
.increase_user_cache_generation();
137 /// Only exposed for testing
139 pub fn test_cfg_from_str(raw
: &str) -> Result
<(SectionConfigData
, [u8;32]), Error
> {
141 let parsed
= cfg
.parse("test_user_cfg", raw
)?
;
146 // shell completion helper
147 pub fn complete_userid(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
149 Ok((data
, _digest
)) => {
151 .filter_map(|(id
, (section_type
, _
))| {
152 if section_type
== "user" {
159 Err(_
) => return vec
![],
163 // shell completion helper
164 pub fn complete_authid(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
166 Ok((data
, _digest
)) => data
.sections
.iter().map(|(id
, _
)| id
.to_string()).collect(),
171 // shell completion helper
172 pub fn complete_token_name(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
173 let data
= match config() {
174 Ok((data
, _digest
)) => data
,
175 Err(_
) => return Vec
::new(),
178 match param
.get("userid") {
180 let user
= data
.lookup
::<User
>("user", userid
);
181 let tokens
= data
.convert_to_typed_array("token");
182 match (user
, tokens
) {
183 (Ok(_
), Ok(tokens
)) => {
186 .filter_map(|token
: ApiToken
| {
187 let tokenid
= token
.tokenid
;
188 if tokenid
.is_token() && tokenid
.user() == userid
{
189 Some(tokenid
.tokenname().unwrap().as_str().to_string())