config: rustfmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
		| @ -10,7 +10,7 @@ use lazy_static::lazy_static; | |||||||
|  |  | ||||||
| use proxmox_schema::{ApiStringFormat, ApiType, Schema, StringSchema}; | use proxmox_schema::{ApiStringFormat, ApiType, Schema, StringSchema}; | ||||||
|  |  | ||||||
| use pbs_api_types::{Authid, Userid, Role, ROLE_NAME_NO_ACCESS}; | use pbs_api_types::{Authid, Role, Userid, ROLE_NAME_NO_ACCESS}; | ||||||
|  |  | ||||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||||
|  |  | ||||||
| @ -328,10 +328,7 @@ impl AclTree { | |||||||
|     fn get_or_insert_node(&mut self, path: &[&str]) -> &mut AclTreeNode { |     fn get_or_insert_node(&mut self, path: &[&str]) -> &mut AclTreeNode { | ||||||
|         let mut node = &mut self.root; |         let mut node = &mut self.root; | ||||||
|         for comp in path { |         for comp in path { | ||||||
|             node = node |             node = node.children.entry(String::from(*comp)).or_default(); | ||||||
|                 .children |  | ||||||
|                 .entry(String::from(*comp)) |  | ||||||
|                 .or_default(); |  | ||||||
|         } |         } | ||||||
|         node |         node | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,15 +1,15 @@ | |||||||
| //! Cached user info for fast ACL permission checks | //! Cached user info for fast ACL permission checks | ||||||
|  |  | ||||||
| use std::sync::{RwLock, Arc}; | use std::sync::{Arc, RwLock}; | ||||||
|  |  | ||||||
| use anyhow::{Error, bail}; | use anyhow::{bail, Error}; | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
|  |  | ||||||
| use proxmox_router::UserInformation; | use proxmox_router::UserInformation; | ||||||
| use proxmox_section_config::SectionConfigData; | use proxmox_section_config::SectionConfigData; | ||||||
| use proxmox_time::epoch_i64; | use proxmox_time::epoch_i64; | ||||||
|  |  | ||||||
| use pbs_api_types::{Authid, Userid, User, ApiToken, ROLE_ADMIN}; | use pbs_api_types::{ApiToken, Authid, User, Userid, ROLE_ADMIN}; | ||||||
|  |  | ||||||
| use crate::acl::{AclTree, ROLE_NAMES}; | use crate::acl::{AclTree, ROLE_NAMES}; | ||||||
| use crate::ConfigVersionCache; | use crate::ConfigVersionCache; | ||||||
| @ -27,13 +27,14 @@ struct ConfigCache { | |||||||
| } | } | ||||||
|  |  | ||||||
| lazy_static! { | lazy_static! { | ||||||
|     static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new( |     static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(ConfigCache { | ||||||
|         ConfigCache { data: None, last_update: 0, last_user_cache_generation: 0 } |         data: None, | ||||||
|     ); |         last_update: 0, | ||||||
|  |         last_user_cache_generation: 0 | ||||||
|  |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| impl CachedUserInfo { | impl CachedUserInfo { | ||||||
|  |  | ||||||
|     /// Returns a cached instance (up to 5 seconds old). |     /// Returns a cached instance (up to 5 seconds old). | ||||||
|     pub fn new() -> Result<Arc<Self>, Error> { |     pub fn new() -> Result<Arc<Self>, Error> { | ||||||
|         let now = epoch_i64(); |         let now = epoch_i64(); | ||||||
| @ -41,10 +42,11 @@ impl CachedUserInfo { | |||||||
|         let version_cache = ConfigVersionCache::new()?; |         let version_cache = ConfigVersionCache::new()?; | ||||||
|         let user_cache_generation = version_cache.user_cache_generation(); |         let user_cache_generation = version_cache.user_cache_generation(); | ||||||
|  |  | ||||||
|         { // limit scope |         { | ||||||
|  |             // limit scope | ||||||
|             let cache = CACHED_CONFIG.read().unwrap(); |             let cache = CACHED_CONFIG.read().unwrap(); | ||||||
|             if (user_cache_generation == cache.last_user_cache_generation) && |             if (user_cache_generation == cache.last_user_cache_generation) | ||||||
|                 ((now - cache.last_update) < 5) |                 && ((now - cache.last_update) < 5) | ||||||
|             { |             { | ||||||
|                 if let Some(ref config) = cache.data { |                 if let Some(ref config) = cache.data { | ||||||
|                     return Ok(config.clone()); |                     return Ok(config.clone()); | ||||||
| @ -92,7 +94,10 @@ impl CachedUserInfo { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if auth_id.is_token() { |         if auth_id.is_token() { | ||||||
|             if let Ok(info) = self.user_cfg.lookup::<ApiToken>("token", &auth_id.to_string()) { |             if let Ok(info) = self | ||||||
|  |                 .user_cfg | ||||||
|  |                 .lookup::<ApiToken>("token", &auth_id.to_string()) | ||||||
|  |             { | ||||||
|                 return info.is_active(); |                 return info.is_active(); | ||||||
|             } else { |             } else { | ||||||
|                 return false; |                 return false; | ||||||
| @ -157,14 +162,14 @@ impl CachedUserInfo { | |||||||
|             // limit privs to that of owning user |             // limit privs to that of owning user | ||||||
|             let user_auth_id = Authid::from(auth_id.user().clone()); |             let user_auth_id = Authid::from(auth_id.user().clone()); | ||||||
|             privs &= self.lookup_privs(&user_auth_id, path); |             privs &= self.lookup_privs(&user_auth_id, path); | ||||||
|             let (owner_privs, owner_propagated_privs) = self.lookup_privs_details(&user_auth_id, path); |             let (owner_privs, owner_propagated_privs) = | ||||||
|  |                 self.lookup_privs_details(&user_auth_id, path); | ||||||
|             privs &= owner_privs; |             privs &= owner_privs; | ||||||
|             propagated_privs &= owner_propagated_privs; |             propagated_privs &= owner_propagated_privs; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         (privs, propagated_privs) |         (privs, propagated_privs) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| impl UserInformation for CachedUserInfo { | impl UserInformation for CachedUserInfo { | ||||||
|  | |||||||
| @ -1,16 +1,17 @@ | |||||||
|  | use std::mem::{ManuallyDrop, MaybeUninit}; | ||||||
| use std::path::Path; | use std::path::Path; | ||||||
| use std::sync::Arc; |  | ||||||
| use std::sync::atomic::{AtomicUsize, Ordering}; | use std::sync::atomic::{AtomicUsize, Ordering}; | ||||||
| use std::mem::{MaybeUninit, ManuallyDrop}; | use std::sync::Arc; | ||||||
|  |  | ||||||
| use anyhow::{bail, Error}; | use anyhow::{bail, Error}; | ||||||
| use once_cell::sync::OnceCell; |  | ||||||
| use nix::sys::stat::Mode; | use nix::sys::stat::Mode; | ||||||
|  | use once_cell::sync::OnceCell; | ||||||
|  |  | ||||||
| use proxmox_sys::fs::{create_path, CreateOptions}; | use proxmox_sys::fs::{create_path, CreateOptions}; | ||||||
|  |  | ||||||
| // openssl::sha::sha256(b"Proxmox Backup ConfigVersionCache v1.0")[0..8]; | // openssl::sha::sha256(b"Proxmox Backup ConfigVersionCache v1.0")[0..8]; | ||||||
| pub const PROXMOX_BACKUP_CONFIG_VERSION_CACHE_MAGIC_1_0: [u8; 8] = [25, 198, 168, 230, 154, 132, 143, 131]; | pub const PROXMOX_BACKUP_CONFIG_VERSION_CACHE_MAGIC_1_0: [u8; 8] = | ||||||
|  |     [25, 198, 168, 230, 154, 132, 143, 131]; | ||||||
|  |  | ||||||
| const FILE_PATH: &str = pbs_buildcfg::rundir!("/shmem/config-versions"); | const FILE_PATH: &str = pbs_buildcfg::rundir!("/shmem/config-versions"); | ||||||
|  |  | ||||||
| @ -26,7 +27,6 @@ struct ConfigVersionCacheDataInner { | |||||||
|     traffic_control_generation: AtomicUsize, |     traffic_control_generation: AtomicUsize, | ||||||
|     // datastore (datastore.cfg) generation/version |     // datastore (datastore.cfg) generation/version | ||||||
|     datastore_generation: AtomicUsize, |     datastore_generation: AtomicUsize, | ||||||
|  |  | ||||||
|     // Add further atomics here |     // Add further atomics here | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -76,15 +76,13 @@ impl Init for ConfigVersionCacheData { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| pub struct ConfigVersionCache { | pub struct ConfigVersionCache { | ||||||
|     shmem: SharedMemory<ConfigVersionCacheData> |     shmem: SharedMemory<ConfigVersionCacheData>, | ||||||
| } | } | ||||||
|  |  | ||||||
| static INSTANCE: OnceCell<Arc< ConfigVersionCache>> = OnceCell::new(); | static INSTANCE: OnceCell<Arc<ConfigVersionCache>> = OnceCell::new(); | ||||||
|  |  | ||||||
| impl ConfigVersionCache { | impl ConfigVersionCache { | ||||||
|  |  | ||||||
|     /// Open the memory based communication channel singleton. |     /// Open the memory based communication channel singleton. | ||||||
|     pub fn new() -> Result<Arc<Self>, Error> { |     pub fn new() -> Result<Arc<Self>, Error> { | ||||||
|         INSTANCE.get_or_try_init(Self::open).map(Arc::clone) |         INSTANCE.get_or_try_init(Self::open).map(Arc::clone) | ||||||
| @ -101,45 +99,47 @@ impl ConfigVersionCache { | |||||||
|  |  | ||||||
|         let file_path = Path::new(FILE_PATH); |         let file_path = Path::new(FILE_PATH); | ||||||
|         let dir_path = file_path.parent().unwrap(); |         let dir_path = file_path.parent().unwrap(); | ||||||
|          |  | ||||||
|         create_path( |         create_path(dir_path, Some(dir_opts.clone()), Some(dir_opts))?; | ||||||
|             dir_path, |  | ||||||
|             Some(dir_opts.clone()), |  | ||||||
|             Some(dir_opts))?; |  | ||||||
|  |  | ||||||
|         let file_opts = CreateOptions::new() |         let file_opts = CreateOptions::new() | ||||||
|             .perm(Mode::from_bits_truncate(0o660)) |             .perm(Mode::from_bits_truncate(0o660)) | ||||||
|             .owner(user.uid) |             .owner(user.uid) | ||||||
|             .group(user.gid); |             .group(user.gid); | ||||||
|  |  | ||||||
|         let shmem: SharedMemory<ConfigVersionCacheData> = |         let shmem: SharedMemory<ConfigVersionCacheData> = SharedMemory::open(file_path, file_opts)?; | ||||||
|             SharedMemory::open(file_path, file_opts)?; |  | ||||||
|  |  | ||||||
|         Ok(Arc::new(Self { shmem })) |         Ok(Arc::new(Self { shmem })) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Returns the user cache generation number. |     /// Returns the user cache generation number. | ||||||
|     pub fn user_cache_generation(&self) -> usize { |     pub fn user_cache_generation(&self) -> usize { | ||||||
|         self.shmem.data() |         self.shmem | ||||||
|             .user_cache_generation.load(Ordering::Acquire) |             .data() | ||||||
|  |             .user_cache_generation | ||||||
|  |             .load(Ordering::Acquire) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Increase the user cache generation number. |     /// Increase the user cache generation number. | ||||||
|     pub fn increase_user_cache_generation(&self) { |     pub fn increase_user_cache_generation(&self) { | ||||||
|         self.shmem.data() |         self.shmem | ||||||
|  |             .data() | ||||||
|             .user_cache_generation |             .user_cache_generation | ||||||
|             .fetch_add(1, Ordering::AcqRel); |             .fetch_add(1, Ordering::AcqRel); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Returns the traffic control generation number. |     /// Returns the traffic control generation number. | ||||||
|     pub fn traffic_control_generation(&self) -> usize { |     pub fn traffic_control_generation(&self) -> usize { | ||||||
|         self.shmem.data() |         self.shmem | ||||||
|             .traffic_control_generation.load(Ordering::Acquire) |             .data() | ||||||
|  |             .traffic_control_generation | ||||||
|  |             .load(Ordering::Acquire) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Increase the traffic control generation number. |     /// Increase the traffic control generation number. | ||||||
|     pub fn increase_traffic_control_generation(&self) { |     pub fn increase_traffic_control_generation(&self) { | ||||||
|         self.shmem.data() |         self.shmem | ||||||
|  |             .data() | ||||||
|             .traffic_control_generation |             .traffic_control_generation | ||||||
|             .fetch_add(1, Ordering::AcqRel); |             .fetch_add(1, Ordering::AcqRel); | ||||||
|     } |     } | ||||||
| @ -156,6 +156,7 @@ impl ConfigVersionCache { | |||||||
|     pub fn increase_datastore_generation(&self) -> usize { |     pub fn increase_datastore_generation(&self) -> usize { | ||||||
|         self.shmem |         self.shmem | ||||||
|             .data() |             .data() | ||||||
|             .datastore_generation.fetch_add(1, Ordering::Acquire) |             .datastore_generation | ||||||
|  |             .fetch_add(1, Ordering::Acquire) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| use anyhow::{Error}; | use anyhow::Error; | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
|  |  | ||||||
| @ -19,7 +19,11 @@ fn init() -> SectionConfig { | |||||||
|         _ => unreachable!(), |         _ => unreachable!(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let plugin = SectionConfigPlugin::new("datastore".to_string(), Some(String::from("name")), obj_schema); |     let plugin = SectionConfigPlugin::new( | ||||||
|  |         "datastore".to_string(), | ||||||
|  |         Some(String::from("name")), | ||||||
|  |         obj_schema, | ||||||
|  |     ); | ||||||
|     let mut config = SectionConfig::new(&DATASTORE_SCHEMA); |     let mut config = SectionConfig::new(&DATASTORE_SCHEMA); | ||||||
|     config.register_plugin(plugin); |     config.register_plugin(plugin); | ||||||
|  |  | ||||||
| @ -34,8 +38,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | |||||||
|     open_backup_lockfile(DATASTORE_CFG_LOCKFILE, None, true) |     open_backup_lockfile(DATASTORE_CFG_LOCKFILE, None, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(DATASTORE_CFG_FILENAME)? |     let content = proxmox_sys::fs::file_read_optional_string(DATASTORE_CFG_FILENAME)? | ||||||
|         .unwrap_or_else(|| "".to_string()); |         .unwrap_or_else(|| "".to_string()); | ||||||
|  |  | ||||||
| @ -96,5 +99,7 @@ pub fn complete_acl_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<St | |||||||
| pub fn complete_calendar_event(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | pub fn complete_calendar_event(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||||
|     // just give some hints about possible values |     // just give some hints about possible values | ||||||
|     ["minutely", "hourly", "daily", "mon..fri", "0:0"] |     ["minutely", "hourly", "daily", "mon..fri", "0:0"] | ||||||
|         .iter().map(|s| String::from(*s)).collect() |         .iter() | ||||||
|  |         .map(|s| String::from(*s)) | ||||||
|  |         .collect() | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,26 +1,29 @@ | |||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
|  |  | ||||||
| use anyhow::{Error}; | use anyhow::Error; | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
|  |  | ||||||
| use proxmox_schema::{ApiType, Schema}; | use proxmox_schema::{ApiType, Schema}; | ||||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||||
|  |  | ||||||
| use pbs_api_types::{OpenIdRealmConfig, REALM_ID_SCHEMA}; |  | ||||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||||
|  | use pbs_api_types::{OpenIdRealmConfig, REALM_ID_SCHEMA}; | ||||||
|  |  | ||||||
| lazy_static! { | lazy_static! { | ||||||
|     pub static ref CONFIG: SectionConfig = init(); |     pub static ref CONFIG: SectionConfig = init(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| fn init() -> SectionConfig { | fn init() -> SectionConfig { | ||||||
|     let obj_schema = match OpenIdRealmConfig::API_SCHEMA { |     let obj_schema = match OpenIdRealmConfig::API_SCHEMA { | ||||||
|         Schema::Object(ref obj_schema) => obj_schema, |         Schema::Object(ref obj_schema) => obj_schema, | ||||||
|         _ => unreachable!(), |         _ => unreachable!(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let plugin = SectionConfigPlugin::new("openid".to_string(), Some(String::from("realm")), obj_schema); |     let plugin = SectionConfigPlugin::new( | ||||||
|  |         "openid".to_string(), | ||||||
|  |         Some(String::from("realm")), | ||||||
|  |         obj_schema, | ||||||
|  |     ); | ||||||
|     let mut config = SectionConfig::new(&REALM_ID_SCHEMA); |     let mut config = SectionConfig::new(&REALM_ID_SCHEMA); | ||||||
|     config.register_plugin(plugin); |     config.register_plugin(plugin); | ||||||
|  |  | ||||||
| @ -35,8 +38,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | |||||||
|     open_backup_lockfile(DOMAINS_CFG_LOCKFILE, None, true) |     open_backup_lockfile(DOMAINS_CFG_LOCKFILE, None, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(DOMAINS_CFG_FILENAME)? |     let content = proxmox_sys::fs::file_read_optional_string(DOMAINS_CFG_FILENAME)? | ||||||
|         .unwrap_or_else(|| "".to_string()); |         .unwrap_or_else(|| "".to_string()); | ||||||
|  |  | ||||||
| @ -60,8 +62,16 @@ pub fn complete_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec< | |||||||
|  |  | ||||||
| pub fn complete_openid_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | pub fn complete_openid_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||||
|     match config() { |     match config() { | ||||||
|         Ok((data, _digest)) => data.sections.iter() |         Ok((data, _digest)) => data | ||||||
|             .filter_map(|(id, (t, _))| if t == "openid" { Some(id.to_string()) } else { None }) |             .sections | ||||||
|  |             .iter() | ||||||
|  |             .filter_map(|(id, (t, _))| { | ||||||
|  |                 if t == "openid" { | ||||||
|  |                     Some(id.to_string()) | ||||||
|  |                 } else { | ||||||
|  |                     None | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|             .collect(), |             .collect(), | ||||||
|         Err(_) => return vec![], |         Err(_) => return vec![], | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -21,17 +21,13 @@ use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlug | |||||||
|  |  | ||||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||||
|  |  | ||||||
| use pbs_api_types::{ | use pbs_api_types::{LtoTapeDrive, ScsiTapeChanger, VirtualTapeDrive, DRIVE_NAME_SCHEMA}; | ||||||
|     DRIVE_NAME_SCHEMA, VirtualTapeDrive, LtoTapeDrive, ScsiTapeChanger, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| lazy_static! { | lazy_static! { | ||||||
|     /// Static [`SectionConfig`] to access parser/writer functions. |     /// Static [`SectionConfig`] to access parser/writer functions. | ||||||
|     pub static ref CONFIG: SectionConfig = init(); |     pub static ref CONFIG: SectionConfig = init(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| fn init() -> SectionConfig { | fn init() -> SectionConfig { | ||||||
|     let mut config = SectionConfig::new(&DRIVE_NAME_SCHEMA); |     let mut config = SectionConfig::new(&DRIVE_NAME_SCHEMA); | ||||||
|  |  | ||||||
| @ -39,7 +35,8 @@ fn init() -> SectionConfig { | |||||||
|         Schema::Object(ref obj_schema) => obj_schema, |         Schema::Object(ref obj_schema) => obj_schema, | ||||||
|         _ => unreachable!(), |         _ => unreachable!(), | ||||||
|     }; |     }; | ||||||
|     let plugin = SectionConfigPlugin::new("virtual".to_string(), Some("name".to_string()), obj_schema); |     let plugin = | ||||||
|  |         SectionConfigPlugin::new("virtual".to_string(), Some("name".to_string()), obj_schema); | ||||||
|     config.register_plugin(plugin); |     config.register_plugin(plugin); | ||||||
|  |  | ||||||
|     let obj_schema = match LtoTapeDrive::API_SCHEMA { |     let obj_schema = match LtoTapeDrive::API_SCHEMA { | ||||||
| @ -53,7 +50,8 @@ fn init() -> SectionConfig { | |||||||
|         Schema::Object(ref obj_schema) => obj_schema, |         Schema::Object(ref obj_schema) => obj_schema, | ||||||
|         _ => unreachable!(), |         _ => unreachable!(), | ||||||
|     }; |     }; | ||||||
|     let plugin = SectionConfigPlugin::new("changer".to_string(), Some("name".to_string()), obj_schema); |     let plugin = | ||||||
|  |         SectionConfigPlugin::new("changer".to_string(), Some("name".to_string()), obj_schema); | ||||||
|     config.register_plugin(plugin); |     config.register_plugin(plugin); | ||||||
|     config |     config | ||||||
| } | } | ||||||
| @ -69,8 +67,7 @@ pub fn lock() -> Result<BackupLockGuard, Error> { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// Read and parse the configuration file | /// Read and parse the configuration file | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(DRIVE_CFG_FILENAME)? |     let content = proxmox_sys::fs::file_read_optional_string(DRIVE_CFG_FILENAME)? | ||||||
|         .unwrap_or_else(|| "".to_string()); |         .unwrap_or_else(|| "".to_string()); | ||||||
|  |  | ||||||
| @ -98,15 +95,12 @@ pub fn check_drive_exists(config: &SectionConfigData, drive: &str) -> Result<(), | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| // shell completion helper | // shell completion helper | ||||||
|  |  | ||||||
| /// List all drive names | /// List all drive names | ||||||
| pub fn complete_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | pub fn complete_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||||
|     match config() { |     match config() { | ||||||
|         Ok((data, _digest)) => data.sections.iter() |         Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(), | ||||||
|             .map(|(id, _)| id.to_string()) |  | ||||||
|             .collect(), |  | ||||||
|         Err(_) => return vec![], |         Err(_) => return vec![], | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -114,10 +108,10 @@ pub fn complete_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec< | |||||||
| /// List Lto tape drives | /// List Lto tape drives | ||||||
| pub fn complete_lto_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | pub fn complete_lto_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||||
|     match config() { |     match config() { | ||||||
|         Ok((data, _digest)) => data.sections.iter() |         Ok((data, _digest)) => data | ||||||
|             .filter(|(_id, (section_type, _))| { |             .sections | ||||||
|                 section_type == "lto" |             .iter() | ||||||
|             }) |             .filter(|(_id, (section_type, _))| section_type == "lto") | ||||||
|             .map(|(id, _)| id.to_string()) |             .map(|(id, _)| id.to_string()) | ||||||
|             .collect(), |             .collect(), | ||||||
|         Err(_) => return vec![], |         Err(_) => return vec![], | ||||||
| @ -127,10 +121,10 @@ pub fn complete_lto_drive_name(_arg: &str, _param: &HashMap<String, String>) -> | |||||||
| /// List Scsi tape changer names | /// List Scsi tape changer names | ||||||
| pub fn complete_changer_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | pub fn complete_changer_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||||
|     match config() { |     match config() { | ||||||
|         Ok((data, _digest)) => data.sections.iter() |         Ok((data, _digest)) => data | ||||||
|             .filter(|(_id, (section_type, _))| { |             .sections | ||||||
|                 section_type == "changer" |             .iter() | ||||||
|             }) |             .filter(|(_id, (section_type, _))| section_type == "changer") | ||||||
|             .map(|(id, _)| id.to_string()) |             .map(|(id, _)| id.to_string()) | ||||||
|             .collect(), |             .collect(), | ||||||
|         Err(_) => return vec![], |         Err(_) => return vec![], | ||||||
|  | |||||||
| @ -4,10 +4,10 @@ use std::path::Path; | |||||||
| use anyhow::{bail, format_err, Context, Error}; | use anyhow::{bail, format_err, Context, Error}; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
|  |  | ||||||
| use proxmox_sys::fs::{file_get_contents, replace_file, CreateOptions}; |  | ||||||
| use proxmox_lang::try_block; | use proxmox_lang::try_block; | ||||||
|  | use proxmox_sys::fs::{file_get_contents, replace_file, CreateOptions}; | ||||||
|  |  | ||||||
| use pbs_api_types::{Kdf, KeyInfo, Fingerprint}; | use pbs_api_types::{Fingerprint, Kdf, KeyInfo}; | ||||||
|  |  | ||||||
| use pbs_tools::crypt_config::CryptConfig; | use pbs_tools::crypt_config::CryptConfig; | ||||||
|  |  | ||||||
| @ -29,28 +29,19 @@ pub enum KeyDerivationConfig { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl KeyDerivationConfig { | impl KeyDerivationConfig { | ||||||
|  |  | ||||||
|     /// Derive a key from provided passphrase |     /// Derive a key from provided passphrase | ||||||
|     pub fn derive_key(&self, passphrase: &[u8]) -> Result<[u8; 32], Error> { |     pub fn derive_key(&self, passphrase: &[u8]) -> Result<[u8; 32], Error> { | ||||||
|  |  | ||||||
|         let mut key = [0u8; 32]; |         let mut key = [0u8; 32]; | ||||||
|  |  | ||||||
|         match self { |         match self { | ||||||
|             KeyDerivationConfig::Scrypt { n, r, p, salt } => { |             KeyDerivationConfig::Scrypt { n, r, p, salt } => { | ||||||
|                 // estimated scrypt memory usage is 128*r*n*p |                 // estimated scrypt memory usage is 128*r*n*p | ||||||
|                 openssl::pkcs5::scrypt( |                 openssl::pkcs5::scrypt(passphrase, salt, *n, *r, *p, 1025 * 1024 * 1024, &mut key)?; | ||||||
|                     passphrase, |  | ||||||
|                     salt, |  | ||||||
|                     *n, *r, *p, |  | ||||||
|                     1025*1024*1024, |  | ||||||
|                     &mut key, |  | ||||||
|                 )?; |  | ||||||
|  |  | ||||||
|                 Ok(key) |                 Ok(key) | ||||||
|             } |             } | ||||||
|             KeyDerivationConfig::PBKDF2 { iter, salt } => { |             KeyDerivationConfig::PBKDF2 { iter, salt } => { | ||||||
|  |                 openssl::pkcs5::pbkdf2_hmac( | ||||||
|                  openssl::pkcs5::pbkdf2_hmac( |  | ||||||
|                     passphrase, |                     passphrase, | ||||||
|                     salt, |                     salt, | ||||||
|                     *iter, |                     *iter, | ||||||
| @ -97,19 +88,15 @@ impl From<&KeyConfig> for KeyInfo { | |||||||
|             }, |             }, | ||||||
|             created: key_config.created, |             created: key_config.created, | ||||||
|             modified: key_config.modified, |             modified: key_config.modified, | ||||||
|             fingerprint: key_config |             fingerprint: key_config.fingerprint.as_ref().map(|fp| fp.signature()), | ||||||
|                 .fingerprint |  | ||||||
|                 .as_ref() |  | ||||||
|                 .map(|fp| fp.signature()), |  | ||||||
|             hint: key_config.hint.clone(), |             hint: key_config.hint.clone(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl KeyConfig  { | impl KeyConfig { | ||||||
|  |  | ||||||
|     /// Creates a new key using random data, protected by passphrase. |     /// Creates a new key using random data, protected by passphrase. | ||||||
|     pub fn new(passphrase: &[u8], kdf: Kdf) -> Result<([u8;32], Self), Error> { |     pub fn new(passphrase: &[u8], kdf: Kdf) -> Result<([u8; 32], Self), Error> { | ||||||
|         let mut key = [0u8; 32]; |         let mut key = [0u8; 32]; | ||||||
|         proxmox_sys::linux::fill_with_random_data(&mut key)?; |         proxmox_sys::linux::fill_with_random_data(&mut key)?; | ||||||
|         let key_config = Self::with_key(&key, passphrase, kdf)?; |         let key_config = Self::with_key(&key, passphrase, kdf)?; | ||||||
| @ -134,12 +121,7 @@ impl KeyConfig  { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Creates a new instance, protect raw_key with passphrase. |     /// Creates a new instance, protect raw_key with passphrase. | ||||||
|     pub fn with_key( |     pub fn with_key(raw_key: &[u8; 32], passphrase: &[u8], kdf: Kdf) -> Result<Self, Error> { | ||||||
|         raw_key: &[u8; 32], |  | ||||||
|         passphrase: &[u8], |  | ||||||
|         kdf: Kdf, |  | ||||||
|     ) -> Result<Self, Error> { |  | ||||||
|  |  | ||||||
|         if raw_key.len() != 32 { |         if raw_key.len() != 32 { | ||||||
|             bail!("got strange key length ({} != 32)", raw_key.len()) |             bail!("got strange key length ({} != 32)", raw_key.len()) | ||||||
|         } |         } | ||||||
| @ -153,10 +135,7 @@ impl KeyConfig  { | |||||||
|                 p: 1, |                 p: 1, | ||||||
|                 salt, |                 salt, | ||||||
|             }, |             }, | ||||||
|             Kdf::PBKDF2 => KeyDerivationConfig::PBKDF2 { |             Kdf::PBKDF2 => KeyDerivationConfig::PBKDF2 { iter: 65535, salt }, | ||||||
|                 iter: 65535, |  | ||||||
|                 salt, |  | ||||||
|             }, |  | ||||||
|             Kdf::None => { |             Kdf::None => { | ||||||
|                 bail!("No key derivation function specified"); |                 bail!("No key derivation function specified"); | ||||||
|             } |             } | ||||||
| @ -169,14 +148,8 @@ impl KeyConfig  { | |||||||
|         let iv = proxmox_sys::linux::random_data(16)?; |         let iv = proxmox_sys::linux::random_data(16)?; | ||||||
|         let mut tag = [0u8; 16]; |         let mut tag = [0u8; 16]; | ||||||
|  |  | ||||||
|         let encrypted_key = openssl::symm::encrypt_aead( |         let encrypted_key = | ||||||
|             cipher, |             openssl::symm::encrypt_aead(cipher, &derived_key, Some(&iv), b"", raw_key, &mut tag)?; | ||||||
|             &derived_key, |  | ||||||
|             Some(&iv), |  | ||||||
|             b"", |  | ||||||
|             raw_key, |  | ||||||
|             &mut tag, |  | ||||||
|         )?; |  | ||||||
|  |  | ||||||
|         let mut enc_data = vec![]; |         let mut enc_data = vec![]; | ||||||
|         enc_data.extend_from_slice(&iv); |         enc_data.extend_from_slice(&iv); | ||||||
| @ -210,12 +183,10 @@ impl KeyConfig  { | |||||||
|     pub fn decrypt( |     pub fn decrypt( | ||||||
|         &self, |         &self, | ||||||
|         passphrase: &dyn Fn() -> Result<Vec<u8>, Error>, |         passphrase: &dyn Fn() -> Result<Vec<u8>, Error>, | ||||||
|     ) -> Result<([u8;32], i64, Fingerprint), Error> { |     ) -> Result<([u8; 32], i64, Fingerprint), Error> { | ||||||
|  |  | ||||||
|         let raw_data = &self.data; |         let raw_data = &self.data; | ||||||
|  |  | ||||||
|         let key = if let Some(ref kdf) = self.kdf { |         let key = if let Some(ref kdf) = self.kdf { | ||||||
|  |  | ||||||
|             let passphrase = passphrase()?; |             let passphrase = passphrase()?; | ||||||
|             if passphrase.len() < 5 { |             if passphrase.len() < 5 { | ||||||
|                 bail!("Passphrase is too short!"); |                 bail!("Passphrase is too short!"); | ||||||
| @ -232,24 +203,15 @@ impl KeyConfig  { | |||||||
|  |  | ||||||
|             let cipher = openssl::symm::Cipher::aes_256_gcm(); |             let cipher = openssl::symm::Cipher::aes_256_gcm(); | ||||||
|  |  | ||||||
|             openssl::symm::decrypt_aead( |             openssl::symm::decrypt_aead(cipher, &derived_key, Some(iv), b"", enc_data, tag) | ||||||
|                 cipher, |                 .map_err(|err| match self.hint { | ||||||
|                 &derived_key, |  | ||||||
|                 Some(iv), |  | ||||||
|                 b"", |  | ||||||
|                 enc_data, |  | ||||||
|                 tag, |  | ||||||
|             ).map_err(|err| { |  | ||||||
|                 match self.hint { |  | ||||||
|                     Some(ref hint) => { |                     Some(ref hint) => { | ||||||
|                         format_err!("Unable to decrypt key (password hint: {})", hint) |                         format_err!("Unable to decrypt key (password hint: {})", hint) | ||||||
|                     } |                     } | ||||||
|                     None => { |                     None => { | ||||||
|                         format_err!("Unable to decrypt key (wrong password?) - {}", err) |                         format_err!("Unable to decrypt key (wrong password?) - {}", err) | ||||||
|                     } |                     } | ||||||
|                 } |                 })? | ||||||
|             })? |  | ||||||
|  |  | ||||||
|         } else { |         } else { | ||||||
|             raw_data.clone() |             raw_data.clone() | ||||||
|         }; |         }; | ||||||
| @ -263,7 +225,8 @@ impl KeyConfig  { | |||||||
|             if &fingerprint != stored_fingerprint { |             if &fingerprint != stored_fingerprint { | ||||||
|                 bail!( |                 bail!( | ||||||
|                     "KeyConfig contains wrong fingerprint {}, contained key has fingerprint {}", |                     "KeyConfig contains wrong fingerprint {}, contained key has fingerprint {}", | ||||||
|                     stored_fingerprint, fingerprint |                     stored_fingerprint, | ||||||
|  |                     fingerprint | ||||||
|                 ); |                 ); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -273,7 +236,6 @@ impl KeyConfig  { | |||||||
|  |  | ||||||
|     /// Store a KeyConfig to path |     /// Store a KeyConfig to path | ||||||
|     pub fn store<P: AsRef<Path>>(&self, path: P, replace: bool) -> Result<(), Error> { |     pub fn store<P: AsRef<Path>>(&self, path: P, replace: bool) -> Result<(), Error> { | ||||||
|  |  | ||||||
|         let path: &Path = path.as_ref(); |         let path: &Path = path.as_ref(); | ||||||
|  |  | ||||||
|         let data = serde_json::to_string(self)?; |         let data = serde_json::to_string(self)?; | ||||||
| @ -295,7 +257,8 @@ impl KeyConfig  { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             Ok(()) |             Ok(()) | ||||||
|         }).map_err(|err: Error| format_err!("Unable to store key file {:?} - {}", path, err))?; |         }) | ||||||
|  |         .map_err(|err: Error| format_err!("Unable to store key file {:?} - {}", path, err))?; | ||||||
|  |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| @ -305,7 +268,7 @@ impl KeyConfig  { | |||||||
| pub fn load_and_decrypt_key( | pub fn load_and_decrypt_key( | ||||||
|     path: &std::path::Path, |     path: &std::path::Path, | ||||||
|     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>, |     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>, | ||||||
| ) -> Result<([u8;32], i64, Fingerprint), Error> { | ) -> Result<([u8; 32], i64, Fingerprint), Error> { | ||||||
|     decrypt_key(&file_get_contents(&path)?, passphrase) |     decrypt_key(&file_get_contents(&path)?, passphrase) | ||||||
|         .with_context(|| format!("failed to load decryption key from {:?}", path)) |         .with_context(|| format!("failed to load decryption key from {:?}", path)) | ||||||
| } | } | ||||||
| @ -314,7 +277,7 @@ pub fn load_and_decrypt_key( | |||||||
| pub fn decrypt_key( | pub fn decrypt_key( | ||||||
|     mut keydata: &[u8], |     mut keydata: &[u8], | ||||||
|     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>, |     passphrase: &dyn Fn() -> Result<Vec<u8>, Error>, | ||||||
| ) -> Result<([u8;32], i64, Fingerprint), Error> { | ) -> Result<([u8; 32], i64, Fingerprint), Error> { | ||||||
|     let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?; |     let key_config: KeyConfig = serde_json::from_reader(&mut keydata)?; | ||||||
|     key_config.decrypt(passphrase) |     key_config.decrypt(passphrase) | ||||||
| } | } | ||||||
| @ -382,8 +345,7 @@ fn encrypt_decrypt_test() -> Result<(), Error> { | |||||||
|  |  | ||||||
|     let encrypted = rsa_encrypt_key_config(public, &key).expect("encryption failed"); |     let encrypted = rsa_encrypt_key_config(public, &key).expect("encryption failed"); | ||||||
|     let (decrypted, created, fingerprint) = |     let (decrypted, created, fingerprint) = | ||||||
|         rsa_decrypt_key_config(private, &encrypted, &passphrase) |         rsa_decrypt_key_config(private, &encrypted, &passphrase).expect("decryption failed"); | ||||||
|             .expect("decryption failed"); |  | ||||||
|  |  | ||||||
|     assert_eq!(key.created, created); |     assert_eq!(key.created, created); | ||||||
|     assert_eq!(key.data, decrypted); |     assert_eq!(key.data, decrypted); | ||||||
| @ -404,12 +366,13 @@ fn fingerprint_checks() -> Result<(), Error> { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let expected_fingerprint = Fingerprint::new([ |     let expected_fingerprint = Fingerprint::new([ | ||||||
|             14, 171, 212, 70, 11, 110, 185, 202, 52, 80, 35, 222, 226, 183, 120, 199, 144, 229, 74, |         14, 171, 212, 70, 11, 110, 185, 202, 52, 80, 35, 222, 226, 183, 120, 199, 144, 229, 74, 22, | ||||||
|             22, 131, 185, 101, 156, 10, 87, 174, 25, 144, 144, 21, 155, |         131, 185, 101, 156, 10, 87, 174, 25, 144, 144, 21, 155, | ||||||
|         ]); |     ]); | ||||||
|  |  | ||||||
|     let mut data = serde_json::to_vec(&key).expect("encoding KeyConfig failed"); |     let mut data = serde_json::to_vec(&key).expect("encoding KeyConfig failed"); | ||||||
|     decrypt_key(&mut data, &{ || { Ok(Vec::new()) }}).expect_err("decoding KeyConfig with wrong fingerprint worked"); |     decrypt_key(&mut data, &{ || Ok(Vec::new()) }) | ||||||
|  |         .expect_err("decoding KeyConfig with wrong fingerprint worked"); | ||||||
|  |  | ||||||
|     let key = KeyConfig { |     let key = KeyConfig { | ||||||
|         kdf: None, |         kdf: None, | ||||||
| @ -420,9 +383,9 @@ fn fingerprint_checks() -> Result<(), Error> { | |||||||
|         hint: None, |         hint: None, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|     let mut data = serde_json::to_vec(&key).expect("encoding KeyConfig failed"); |     let mut data = serde_json::to_vec(&key).expect("encoding KeyConfig failed"); | ||||||
|     let (key_data, created, fingerprint) = decrypt_key(&mut data, &{ || { Ok(Vec::new()) }}).expect("decoding KeyConfig without fingerprint failed"); |     let (key_data, created, fingerprint) = decrypt_key(&mut data, &{ || Ok(Vec::new()) }) | ||||||
|  |         .expect("decoding KeyConfig without fingerprint failed"); | ||||||
|  |  | ||||||
|     assert_eq!(key.data, key_data); |     assert_eq!(key.data, key_data); | ||||||
|     assert_eq!(key.created, created); |     assert_eq!(key.created, created); | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ pub use config_version_cache::ConfigVersionCache; | |||||||
| use anyhow::{format_err, Error}; | use anyhow::{format_err, Error}; | ||||||
| use nix::unistd::{Gid, Group, Uid, User}; | use nix::unistd::{Gid, Group, Uid, User}; | ||||||
|  |  | ||||||
| pub use pbs_buildcfg::{BACKUP_USER_NAME, BACKUP_GROUP_NAME}; | pub use pbs_buildcfg::{BACKUP_GROUP_NAME, BACKUP_USER_NAME}; | ||||||
|  |  | ||||||
| /// Return User info for the 'backup' user (``getpwnam_r(3)``) | /// Return User info for the 'backup' user (``getpwnam_r(3)``) | ||||||
| pub fn backup_user() -> Result<nix::unistd::User, Error> { | pub fn backup_user() -> Result<nix::unistd::User, Error> { | ||||||
| @ -79,10 +79,7 @@ pub fn open_backup_lockfile<P: AsRef<std::path::Path>>( | |||||||
| /// Atomically write data to file owned by "root:backup" with permission "0640" | /// Atomically write data to file owned by "root:backup" with permission "0640" | ||||||
| /// | /// | ||||||
| /// Only the superuser can write those files, but group 'backup' can read them. | /// Only the superuser can write those files, but group 'backup' can read them. | ||||||
| pub fn replace_backup_config<P: AsRef<std::path::Path>>( | pub fn replace_backup_config<P: AsRef<std::path::Path>>(path: P, data: &[u8]) -> Result<(), Error> { | ||||||
|     path: P, |  | ||||||
|     data: &[u8], |  | ||||||
| ) -> Result<(), Error> { |  | ||||||
|     let backup_user = backup_user()?; |     let backup_user = backup_user()?; | ||||||
|     let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); |     let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640); | ||||||
|     // set the correct owner/group/permissions while saving file |     // set the correct owner/group/permissions while saving file | ||||||
| @ -100,10 +97,7 @@ pub fn replace_backup_config<P: AsRef<std::path::Path>>( | |||||||
| /// Atomically write data to file owned by "root:root" with permission "0600" | /// Atomically write data to file owned by "root:root" with permission "0600" | ||||||
| /// | /// | ||||||
| /// Only the superuser can read and write those files. | /// Only the superuser can read and write those files. | ||||||
| pub fn replace_secret_config<P: AsRef<std::path::Path>>( | pub fn replace_secret_config<P: AsRef<std::path::Path>>(path: P, data: &[u8]) -> Result<(), Error> { | ||||||
|     path: P, |  | ||||||
|     data: &[u8], |  | ||||||
| ) -> Result<(), Error> { |  | ||||||
|     let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); |     let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); | ||||||
|     // set the correct owner/group/permissions while saving file |     // set the correct owner/group/permissions while saving file | ||||||
|     // owner(rw) = root, group(r)= root |     // owner(rw) = root, group(r)= root | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ use lazy_static::lazy_static; | |||||||
| use proxmox_schema::*; | use proxmox_schema::*; | ||||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||||
|  |  | ||||||
| use pbs_api_types::{MEDIA_POOL_NAME_SCHEMA, MediaPoolConfig}; | use pbs_api_types::{MediaPoolConfig, MEDIA_POOL_NAME_SCHEMA}; | ||||||
|  |  | ||||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||||
|  |  | ||||||
| @ -47,8 +47,7 @@ pub fn lock() -> Result<BackupLockGuard, Error> { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// Read and parse the configuration file | /// Read and parse the configuration file | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(MEDIA_POOL_CFG_FILENAME)? |     let content = proxmox_sys::fs::file_read_optional_string(MEDIA_POOL_CFG_FILENAME)? | ||||||
|         .unwrap_or_else(|| "".to_string()); |         .unwrap_or_else(|| "".to_string()); | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,12 +1,12 @@ | |||||||
| use std::path::Path; |  | ||||||
| use std::process::Command; |  | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::os::unix::io::{AsRawFd, FromRawFd}; | use std::os::unix::io::{AsRawFd, FromRawFd}; | ||||||
|  | use std::path::Path; | ||||||
|  | use std::process::Command; | ||||||
|  |  | ||||||
| use anyhow::{Error, bail, format_err}; | use anyhow::{bail, format_err, Error}; | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use nix::ioctl_read_bad; | use nix::ioctl_read_bad; | ||||||
| use nix::sys::socket::{socket, AddressFamily, SockType, SockFlag}; | use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType}; | ||||||
| use regex::Regex; | use regex::Regex; | ||||||
|  |  | ||||||
| use pbs_api_types::*; // for IP macros | use pbs_api_types::*; // for IP macros | ||||||
| @ -77,7 +77,13 @@ pub fn check_netmask(mask: u8, is_v6: bool) -> Result<(), Error> { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     if !(mask >= min && mask <= max) { |     if !(mask >= min && mask <= max) { | ||||||
|         bail!("{} mask '{}' is out of range ({}..{}).", ver, mask, min, max); |         bail!( | ||||||
|  |             "{} mask '{}' is out of range ({}..{}).", | ||||||
|  |             ver, | ||||||
|  |             mask, | ||||||
|  |             min, | ||||||
|  |             max | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Ok(()) |     Ok(()) | ||||||
| @ -85,14 +91,11 @@ pub fn check_netmask(mask: u8, is_v6: bool) -> Result<(), Error> { | |||||||
|  |  | ||||||
| // parse ip address with optional cidr mask | // parse ip address with optional cidr mask | ||||||
| pub fn parse_address_or_cidr(cidr: &str) -> Result<(String, Option<u8>, bool), Error> { | pub fn parse_address_or_cidr(cidr: &str) -> Result<(String, Option<u8>, bool), Error> { | ||||||
|  |  | ||||||
|     lazy_static! { |     lazy_static! { | ||||||
|         pub static ref CIDR_V4_REGEX: Regex = Regex::new( |         pub static ref CIDR_V4_REGEX: Regex = | ||||||
|             concat!(r"^(", IPV4RE!(), r")(?:/(\d{1,2}))?$") |             Regex::new(concat!(r"^(", IPV4RE!(), r")(?:/(\d{1,2}))?$")).unwrap(); | ||||||
|         ).unwrap(); |         pub static ref CIDR_V6_REGEX: Regex = | ||||||
|         pub static ref CIDR_V6_REGEX: Regex = Regex::new( |             Regex::new(concat!(r"^(", IPV6RE!(), r")(?:/(\d{1,3}))?$")).unwrap(); | ||||||
|             concat!(r"^(", IPV6RE!(), r")(?:/(\d{1,3}))?$") |  | ||||||
|         ).unwrap(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if let Some(caps) = CIDR_V4_REGEX.captures(cidr) { |     if let Some(caps) = CIDR_V4_REGEX.captures(cidr) { | ||||||
| @ -119,7 +122,6 @@ pub fn parse_address_or_cidr(cidr: &str) -> Result<(String, Option<u8>, bool), E | |||||||
| } | } | ||||||
|  |  | ||||||
| pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { | pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { | ||||||
|  |  | ||||||
|     const PROC_NET_DEV: &str = "/proc/net/dev"; |     const PROC_NET_DEV: &str = "/proc/net/dev"; | ||||||
|  |  | ||||||
|     #[repr(C)] |     #[repr(C)] | ||||||
| @ -130,7 +132,7 @@ pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { | |||||||
|  |  | ||||||
|     ioctl_read_bad!(get_interface_flags, libc::SIOCGIFFLAGS, ifreq); |     ioctl_read_bad!(get_interface_flags, libc::SIOCGIFFLAGS, ifreq); | ||||||
|  |  | ||||||
|     lazy_static!{ |     lazy_static! { | ||||||
|         static ref IFACE_LINE_REGEX: Regex = Regex::new(r"^\s*([^:\s]+):").unwrap(); |         static ref IFACE_LINE_REGEX: Regex = Regex::new(r"^\s*([^:\s]+):").unwrap(); | ||||||
|     } |     } | ||||||
|     let raw = std::fs::read_to_string(PROC_NET_DEV) |     let raw = std::fs::read_to_string(PROC_NET_DEV) | ||||||
| @ -163,13 +165,26 @@ pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { | |||||||
|         if let Some(cap) = IFACE_LINE_REGEX.captures(line) { |         if let Some(cap) = IFACE_LINE_REGEX.captures(line) { | ||||||
|             let ifname = &cap[1]; |             let ifname = &cap[1]; | ||||||
|  |  | ||||||
|             let mut req = ifreq { ifr_name: *b"0000000000000000", ifru_flags: 0 }; |             let mut req = ifreq { | ||||||
|             for (i, b) in std::ffi::CString::new(ifname)?.as_bytes_with_nul().iter().enumerate() { |                 ifr_name: *b"0000000000000000", | ||||||
|                 if i < (libc::IFNAMSIZ-1) { req.ifr_name[i] = *b as libc::c_uchar; } |                 ifru_flags: 0, | ||||||
|  |             }; | ||||||
|  |             for (i, b) in std::ffi::CString::new(ifname)? | ||||||
|  |                 .as_bytes_with_nul() | ||||||
|  |                 .iter() | ||||||
|  |                 .enumerate() | ||||||
|  |             { | ||||||
|  |                 if i < (libc::IFNAMSIZ - 1) { | ||||||
|  |                     req.ifr_name[i] = *b as libc::c_uchar; | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             let res = unsafe { get_interface_flags(sock.as_raw_fd(), &mut req)? }; |             let res = unsafe { get_interface_flags(sock.as_raw_fd(), &mut req)? }; | ||||||
|             if res != 0 { |             if res != 0 { | ||||||
|                 bail!("ioctl get_interface_flags for '{}' failed ({})", ifname, res); |                 bail!( | ||||||
|  |                     "ioctl get_interface_flags for '{}' failed ({})", | ||||||
|  |                     ifname, | ||||||
|  |                     res | ||||||
|  |                 ); | ||||||
|             } |             } | ||||||
|             let is_up = (req.ifru_flags & (libc::IFF_UP as libc::c_short)) != 0; |             let is_up = (req.ifru_flags & (libc::IFF_UP as libc::c_short)) != 0; | ||||||
|             interface_list.insert(ifname.to_string(), is_up); |             interface_list.insert(ifname.to_string(), is_up); | ||||||
| @ -180,7 +195,6 @@ pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { | |||||||
| } | } | ||||||
|  |  | ||||||
| pub fn compute_file_diff(filename: &str, shadow: &str) -> Result<String, Error> { | pub fn compute_file_diff(filename: &str, shadow: &str) -> Result<String, Error> { | ||||||
|  |  | ||||||
|     let output = Command::new("diff") |     let output = Command::new("diff") | ||||||
|         .arg("-b") |         .arg("-b") | ||||||
|         .arg("-u") |         .arg("-u") | ||||||
| @ -204,7 +218,6 @@ pub fn assert_ifupdown2_installed() -> Result<(), Error> { | |||||||
| } | } | ||||||
|  |  | ||||||
| pub fn network_reload() -> Result<(), Error> { | pub fn network_reload() -> Result<(), Error> { | ||||||
|  |  | ||||||
|     let output = Command::new("ifreload") |     let output = Command::new("ifreload") | ||||||
|         .arg("-a") |         .arg("-a") | ||||||
|         .output() |         .output() | ||||||
| @ -213,6 +226,5 @@ pub fn network_reload() -> Result<(), Error> { | |||||||
|     proxmox_sys::command::command_output(output, None) |     proxmox_sys::command::command_output(output, None) | ||||||
|         .map_err(|err| format_err!("ifreload failed: {}", err))?; |         .map_err(|err| format_err!("ifreload failed: {}", err))?; | ||||||
|  |  | ||||||
|  |  | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
|  | use std::collections::{HashMap, VecDeque}; | ||||||
| use std::io::BufRead; | use std::io::BufRead; | ||||||
| use std::iter::Iterator; | use std::iter::Iterator; | ||||||
| use std::collections::{HashMap, VecDeque}; |  | ||||||
|  |  | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
|  |  | ||||||
| @ -67,10 +67,13 @@ pub struct Lexer<R> { | |||||||
|     cur_line: Option<VecDeque<(Token, String)>>, |     cur_line: Option<VecDeque<(Token, String)>>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl <R: BufRead> Lexer<R> { | impl<R: BufRead> Lexer<R> { | ||||||
|  |  | ||||||
|     pub fn new(input: R) -> Self { |     pub fn new(input: R) -> Self { | ||||||
|         Self { input, eof_count: 0, cur_line: None } |         Self { | ||||||
|  |             input, | ||||||
|  |             eof_count: 0, | ||||||
|  |             cur_line: None, | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn split_line(line: &str) -> VecDeque<(Token, String)> { |     fn split_line(line: &str) -> VecDeque<(Token, String)> { | ||||||
| @ -79,10 +82,13 @@ impl <R: BufRead> Lexer<R> { | |||||||
|             res.push_back((Token::Comment, comment.trim().to_string())); |             res.push_back((Token::Comment, comment.trim().to_string())); | ||||||
|             return res; |             return res; | ||||||
|         } |         } | ||||||
|         let mut list: VecDeque<(Token, String)> = line.split_ascii_whitespace().map(|text| { |         let mut list: VecDeque<(Token, String)> = line | ||||||
|             let token = KEYWORDS.get(text).unwrap_or(&Token::Text); |             .split_ascii_whitespace() | ||||||
|             (*token, text.to_string()) |             .map(|text| { | ||||||
|         }).collect(); |                 let token = KEYWORDS.get(text).unwrap_or(&Token::Text); | ||||||
|  |                 (*token, text.to_string()) | ||||||
|  |             }) | ||||||
|  |             .collect(); | ||||||
|  |  | ||||||
|         if line.starts_with(|c: char| c.is_ascii_whitespace() && c != '\n') { |         if line.starts_with(|c: char| c.is_ascii_whitespace() && c != '\n') { | ||||||
|             list.push_front((Token::Attribute, String::from("\t"))); |             list.push_front((Token::Attribute, String::from("\t"))); | ||||||
| @ -91,8 +97,7 @@ impl <R: BufRead> Lexer<R> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl <R: BufRead> Iterator for Lexer<R> { | impl<R: BufRead> Iterator for Lexer<R> { | ||||||
|  |  | ||||||
|     type Item = Result<(Token, String), std::io::Error>; |     type Item = Result<(Token, String), std::io::Error>; | ||||||
|  |  | ||||||
|     fn next(&mut self) -> Option<Self::Item> { |     fn next(&mut self) -> Option<Self::Item> { | ||||||
| @ -102,7 +107,9 @@ impl <R: BufRead> Iterator for Lexer<R> { | |||||||
|                 Err(err) => return Some(Err(err)), |                 Err(err) => return Some(Err(err)), | ||||||
|                 Ok(0) => { |                 Ok(0) => { | ||||||
|                     self.eof_count += 1; |                     self.eof_count += 1; | ||||||
|                     if self.eof_count == 1 { return Some(Ok((Token::EOF, String::new()))); } |                     if self.eof_count == 1 { | ||||||
|  |                         return Some(Ok((Token::EOF, String::new()))); | ||||||
|  |                     } | ||||||
|                     return None; |                     return None; | ||||||
|                 } |                 } | ||||||
|                 _ => {} |                 _ => {} | ||||||
| @ -111,7 +118,7 @@ impl <R: BufRead> Iterator for Lexer<R> { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         match self.cur_line { |         match self.cur_line { | ||||||
|             Some(ref mut  cur_line) => { |             Some(ref mut cur_line) => { | ||||||
|                 if cur_line.is_empty() { |                 if cur_line.is_empty() { | ||||||
|                     self.cur_line = None; |                     self.cur_line = None; | ||||||
|                     Some(Ok((Token::Newline, String::from("\n")))) |                     Some(Ok((Token::Newline, String::from("\n")))) | ||||||
| @ -120,9 +127,7 @@ impl <R: BufRead> Iterator for Lexer<R> { | |||||||
|                     Some(Ok((token, text))) |                     Some(Ok((token, text))) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             None => { |             None => None, | ||||||
|                 None |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| use std::io::{Write}; | use std::collections::{BTreeMap, HashMap, HashSet}; | ||||||
| use std::collections::{HashSet, HashMap, BTreeMap}; | use std::io::Write; | ||||||
|  |  | ||||||
| use anyhow::{Error, format_err, bail}; | use anyhow::{bail, format_err, Error}; | ||||||
| use serde::de::{value, IntoDeserializer, Deserialize}; |  | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use regex::Regex; | use regex::Regex; | ||||||
|  | use serde::de::{value, Deserialize, IntoDeserializer}; | ||||||
|  |  | ||||||
| use proxmox_sys::{fs::replace_file, fs::CreateOptions}; | use proxmox_sys::{fs::replace_file, fs::CreateOptions}; | ||||||
|  |  | ||||||
| @ -17,11 +17,13 @@ pub use lexer::*; | |||||||
| mod parser; | mod parser; | ||||||
| pub use parser::*; | pub use parser::*; | ||||||
|  |  | ||||||
| use pbs_api_types::{Interface, NetworkConfigMethod, NetworkInterfaceType, LinuxBondMode, BondXmitHashPolicy}; | use pbs_api_types::{ | ||||||
|  |     BondXmitHashPolicy, Interface, LinuxBondMode, NetworkConfigMethod, NetworkInterfaceType, | ||||||
|  | }; | ||||||
|  |  | ||||||
| use crate::{open_backup_lockfile, BackupLockGuard}; | use crate::{open_backup_lockfile, BackupLockGuard}; | ||||||
|  |  | ||||||
| lazy_static!{ | lazy_static! { | ||||||
|     static ref PHYSICAL_NIC_REGEX: Regex = Regex::new(r"^(?:eth\d+|en[^:.]+|ib\d+)$").unwrap(); |     static ref PHYSICAL_NIC_REGEX: Regex = Regex::new(r"^(?:eth\d+|en[^:.]+|ib\d+)$").unwrap(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -61,7 +63,6 @@ pub fn bond_xmit_hash_policy_to_str(policy: &BondXmitHashPolicy) -> &'static str | |||||||
|  |  | ||||||
| // Write attributes not depending on address family | // Write attributes not depending on address family | ||||||
| fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | ||||||
|  |  | ||||||
|     static EMPTY_LIST: Vec<String> = Vec::new(); |     static EMPTY_LIST: Vec<String> = Vec::new(); | ||||||
|  |  | ||||||
|     match iface.interface_type { |     match iface.interface_type { | ||||||
| @ -86,10 +87,12 @@ fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Er | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             if let Some(xmit_policy) = &iface.bond_xmit_hash_policy { |             if let Some(xmit_policy) = &iface.bond_xmit_hash_policy { | ||||||
|                 if mode == LinuxBondMode::ieee802_3ad || |                 if mode == LinuxBondMode::ieee802_3ad || mode == LinuxBondMode::balance_xor { | ||||||
|                     mode == LinuxBondMode::balance_xor |                     writeln!( | ||||||
|                 { |                         w, | ||||||
|                     writeln!(w, "\tbond_xmit_hash_policy {}", bond_xmit_hash_policy_to_str(xmit_policy))?; |                         "\tbond_xmit_hash_policy {}", | ||||||
|  |                         bond_xmit_hash_policy_to_str(xmit_policy) | ||||||
|  |                     )?; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @ -111,7 +114,11 @@ fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Er | |||||||
| } | } | ||||||
|  |  | ||||||
| // Write attributes depending on address family inet (IPv4) | // Write attributes depending on address family inet (IPv4) | ||||||
| fn write_iface_attributes_v4(iface: &Interface, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> { | fn write_iface_attributes_v4( | ||||||
|  |     iface: &Interface, | ||||||
|  |     w: &mut dyn Write, | ||||||
|  |     method: NetworkConfigMethod, | ||||||
|  | ) -> Result<(), Error> { | ||||||
|     if method == NetworkConfigMethod::Static { |     if method == NetworkConfigMethod::Static { | ||||||
|         if let Some(address) = &iface.cidr { |         if let Some(address) = &iface.cidr { | ||||||
|             writeln!(w, "\taddress {}", address)?; |             writeln!(w, "\taddress {}", address)?; | ||||||
| @ -135,7 +142,11 @@ fn write_iface_attributes_v4(iface: &Interface, w: &mut dyn Write, method: Netwo | |||||||
| } | } | ||||||
|  |  | ||||||
| /// Write attributes depending on address family inet6 (IPv6) | /// Write attributes depending on address family inet6 (IPv6) | ||||||
| fn write_iface_attributes_v6(iface: &Interface, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> { | fn write_iface_attributes_v6( | ||||||
|  |     iface: &Interface, | ||||||
|  |     w: &mut dyn Write, | ||||||
|  |     method: NetworkConfigMethod, | ||||||
|  | ) -> Result<(), Error> { | ||||||
|     if method == NetworkConfigMethod::Static { |     if method == NetworkConfigMethod::Static { | ||||||
|         if let Some(address) = &iface.cidr6 { |         if let Some(address) = &iface.cidr6 { | ||||||
|             writeln!(w, "\taddress {}", address)?; |             writeln!(w, "\taddress {}", address)?; | ||||||
| @ -159,7 +170,6 @@ fn write_iface_attributes_v6(iface: &Interface, w: &mut dyn Write, method: Netwo | |||||||
| } | } | ||||||
|  |  | ||||||
| fn write_iface(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | fn write_iface(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | ||||||
|  |  | ||||||
|     fn method_to_str(method: NetworkConfigMethod) -> &'static str { |     fn method_to_str(method: NetworkConfigMethod) -> &'static str { | ||||||
|         match method { |         match method { | ||||||
|             NetworkConfigMethod::Static => "static", |             NetworkConfigMethod::Static => "static", | ||||||
| @ -169,7 +179,9 @@ fn write_iface(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if iface.method.is_none() && iface.method6.is_none() { return Ok(()); } |     if iface.method.is_none() && iface.method6.is_none() { | ||||||
|  |         return Ok(()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if iface.autostart { |     if iface.autostart { | ||||||
|         writeln!(w, "auto {}", iface.name)?; |         writeln!(w, "auto {}", iface.name)?; | ||||||
| @ -195,7 +207,8 @@ fn write_iface(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> { | |||||||
|         if !skip_v6 { |         if !skip_v6 { | ||||||
|             writeln!(w, "iface {} inet6 {}", iface.name, method_to_str(method6))?; |             writeln!(w, "iface {} inet6 {}", iface.name, method_to_str(method6))?; | ||||||
|             write_iface_attributes_v6(iface, w, method6)?; |             write_iface_attributes_v6(iface, w, method6)?; | ||||||
|             if iface.method.is_none() { // only write common attributes once |             if iface.method.is_none() { | ||||||
|  |                 // only write common attributes once | ||||||
|                 write_iface_attributes(iface, w)?; |                 write_iface_attributes(iface, w)?; | ||||||
|             } |             } | ||||||
|             writeln!(w)?; |             writeln!(w)?; | ||||||
| @ -220,8 +233,7 @@ pub struct NetworkConfig { | |||||||
|  |  | ||||||
| use std::convert::TryFrom; | use std::convert::TryFrom; | ||||||
|  |  | ||||||
| impl TryFrom<NetworkConfig> for String  { | impl TryFrom<NetworkConfig> for String { | ||||||
|  |  | ||||||
|     type Error = Error; |     type Error = Error; | ||||||
|  |  | ||||||
|     fn try_from(config: NetworkConfig) -> Result<Self, Self::Error> { |     fn try_from(config: NetworkConfig) -> Result<Self, Self::Error> { | ||||||
| @ -233,7 +245,6 @@ impl TryFrom<NetworkConfig> for String  { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl NetworkConfig { | impl NetworkConfig { | ||||||
|  |  | ||||||
|     pub fn new() -> Self { |     pub fn new() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             interfaces: BTreeMap::new(), |             interfaces: BTreeMap::new(), | ||||||
| @ -242,16 +253,18 @@ impl NetworkConfig { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn lookup(&self, name: &str) -> Result<&Interface, Error> { |     pub fn lookup(&self, name: &str) -> Result<&Interface, Error> { | ||||||
|         let interface = self.interfaces.get(name).ok_or_else(|| { |         let interface = self | ||||||
|             format_err!("interface '{}' does not exist.", name) |             .interfaces | ||||||
|         })?; |             .get(name) | ||||||
|  |             .ok_or_else(|| format_err!("interface '{}' does not exist.", name))?; | ||||||
|         Ok(interface) |         Ok(interface) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn lookup_mut(&mut self, name: &str) -> Result<&mut Interface, Error> { |     pub fn lookup_mut(&mut self, name: &str) -> Result<&mut Interface, Error> { | ||||||
|         let interface = self.interfaces.get_mut(name).ok_or_else(|| { |         let interface = self | ||||||
|             format_err!("interface '{}' does not exist.", name) |             .interfaces | ||||||
|         })?; |             .get_mut(name) | ||||||
|  |             .ok_or_else(|| format_err!("interface '{}' does not exist.", name))?; | ||||||
|         Ok(interface) |         Ok(interface) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -261,8 +274,12 @@ impl NetworkConfig { | |||||||
|         let mut check_port_usage = |iface, ports: &Vec<String>| { |         let mut check_port_usage = |iface, ports: &Vec<String>| { | ||||||
|             for port in ports.iter() { |             for port in ports.iter() { | ||||||
|                 if let Some(prev_iface) = used_ports.get(port) { |                 if let Some(prev_iface) = used_ports.get(port) { | ||||||
|                     bail!("iface '{}' port '{}' is already used on interface '{}'", |                     bail!( | ||||||
|                           iface, port, prev_iface); |                         "iface '{}' port '{}' is already used on interface '{}'", | ||||||
|  |                         iface, | ||||||
|  |                         port, | ||||||
|  |                         prev_iface | ||||||
|  |                     ); | ||||||
|                 } |                 } | ||||||
|                 used_ports.insert(port.to_string(), iface); |                 used_ports.insert(port.to_string(), iface); | ||||||
|             } |             } | ||||||
| @ -270,18 +287,25 @@ impl NetworkConfig { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         for (iface, interface) in self.interfaces.iter() { |         for (iface, interface) in self.interfaces.iter() { | ||||||
|             if let Some(ports) = &interface.bridge_ports { check_port_usage(iface, ports)?; } |             if let Some(ports) = &interface.bridge_ports { | ||||||
|             if let Some(slaves) = &interface.slaves { check_port_usage(iface, slaves)?; } |                 check_port_usage(iface, ports)?; | ||||||
|  |             } | ||||||
|  |             if let Some(slaves) = &interface.slaves { | ||||||
|  |                 check_port_usage(iface, slaves)?; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Check if child mtu is less or equal than parent mtu |     /// Check if child mtu is less or equal than parent mtu | ||||||
|     pub fn check_mtu(&self, parent_name: &str, child_name: &str) -> Result<(), Error> { |     pub fn check_mtu(&self, parent_name: &str, child_name: &str) -> Result<(), Error> { | ||||||
|  |         let parent = self | ||||||
|         let parent = self.interfaces.get(parent_name) |             .interfaces | ||||||
|  |             .get(parent_name) | ||||||
|             .ok_or_else(|| format_err!("check_mtu - missing parent interface '{}'", parent_name))?; |             .ok_or_else(|| format_err!("check_mtu - missing parent interface '{}'", parent_name))?; | ||||||
|         let child = self.interfaces.get(child_name) |         let child = self | ||||||
|  |             .interfaces | ||||||
|  |             .get(child_name) | ||||||
|             .ok_or_else(|| format_err!("check_mtu - missing child interface '{}'", child_name))?; |             .ok_or_else(|| format_err!("check_mtu - missing child interface '{}'", child_name))?; | ||||||
|  |  | ||||||
|         let child_mtu = match child.mtu { |         let child_mtu = match child.mtu { | ||||||
| @ -301,8 +325,13 @@ impl NetworkConfig { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         if parent_mtu < child_mtu { |         if parent_mtu < child_mtu { | ||||||
|             bail!("interface '{}' - mtu {} is lower than '{}' - mtu {}\n", |             bail!( | ||||||
|                   parent_name, parent_mtu, child_name, child_mtu); |                 "interface '{}' - mtu {} is lower than '{}' - mtu {}\n", | ||||||
|  |                 parent_name, | ||||||
|  |                 parent_mtu, | ||||||
|  |                 child_name, | ||||||
|  |                 child_mtu | ||||||
|  |             ); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
| @ -316,8 +345,13 @@ impl NetworkConfig { | |||||||
|                     match self.interfaces.get(slave) { |                     match self.interfaces.get(slave) { | ||||||
|                         Some(entry) => { |                         Some(entry) => { | ||||||
|                             if entry.interface_type != NetworkInterfaceType::Eth { |                             if entry.interface_type != NetworkInterfaceType::Eth { | ||||||
|                                 bail!("bond '{}' - wrong interface type on slave '{}' ({:?} != {:?})", |                                 bail!( | ||||||
|                                       iface, slave, entry.interface_type, NetworkInterfaceType::Eth); |                                     "bond '{}' - wrong interface type on slave '{}' ({:?} != {:?})", | ||||||
|  |                                     iface, | ||||||
|  |                                     slave, | ||||||
|  |                                     entry.interface_type, | ||||||
|  |                                     NetworkInterfaceType::Eth | ||||||
|  |                                 ); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                         None => { |                         None => { | ||||||
| @ -333,7 +367,7 @@ impl NetworkConfig { | |||||||
|  |  | ||||||
|     /// Check if bridge ports exists |     /// Check if bridge ports exists | ||||||
|     pub fn check_bridge_ports(&self) -> Result<(), Error> { |     pub fn check_bridge_ports(&self) -> Result<(), Error> { | ||||||
|         lazy_static!{ |         lazy_static! { | ||||||
|             static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^(\S+)\.(\d+)$").unwrap(); |             static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^(\S+)\.(\d+)$").unwrap(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @ -341,7 +375,11 @@ impl NetworkConfig { | |||||||
|             if let Some(ports) = &interface.bridge_ports { |             if let Some(ports) = &interface.bridge_ports { | ||||||
|                 for port in ports.iter() { |                 for port in ports.iter() { | ||||||
|                     let captures = VLAN_INTERFACE_REGEX.captures(port); |                     let captures = VLAN_INTERFACE_REGEX.captures(port); | ||||||
|                     let port = if let Some(ref caps) = captures { &caps[1] } else { port.as_str() }; |                     let port = if let Some(ref caps) = captures { | ||||||
|  |                         &caps[1] | ||||||
|  |                     } else { | ||||||
|  |                         port.as_str() | ||||||
|  |                     }; | ||||||
|                     if !self.interfaces.contains_key(port) { |                     if !self.interfaces.contains_key(port) { | ||||||
|                         bail!("bridge '{}' - unable to find port '{}'", iface, port); |                         bail!("bridge '{}' - unable to find port '{}'", iface, port); | ||||||
|                     } |                     } | ||||||
| @ -353,7 +391,6 @@ impl NetworkConfig { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> { |     pub fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> { | ||||||
|  |  | ||||||
|         self.check_port_usage()?; |         self.check_port_usage()?; | ||||||
|         self.check_bond_slaves()?; |         self.check_bond_slaves()?; | ||||||
|         self.check_bridge_ports()?; |         self.check_bridge_ports()?; | ||||||
| @ -363,13 +400,15 @@ impl NetworkConfig { | |||||||
|         let mut last_entry_was_comment = false; |         let mut last_entry_was_comment = false; | ||||||
|  |  | ||||||
|         for entry in self.order.iter() { |         for entry in self.order.iter() { | ||||||
|              match entry { |             match entry { | ||||||
|                 NetworkOrderEntry::Comment(comment) => { |                 NetworkOrderEntry::Comment(comment) => { | ||||||
|                     writeln!(w, "#{}", comment)?; |                     writeln!(w, "#{}", comment)?; | ||||||
|                     last_entry_was_comment = true; |                     last_entry_was_comment = true; | ||||||
|                 } |                 } | ||||||
|                 NetworkOrderEntry::Option(option) => { |                 NetworkOrderEntry::Option(option) => { | ||||||
|                     if last_entry_was_comment {  writeln!(w)?; } |                     if last_entry_was_comment { | ||||||
|  |                         writeln!(w)?; | ||||||
|  |                     } | ||||||
|                     last_entry_was_comment = false; |                     last_entry_was_comment = false; | ||||||
|                     writeln!(w, "{}", option)?; |                     writeln!(w, "{}", option)?; | ||||||
|                     writeln!(w)?; |                     writeln!(w)?; | ||||||
| @ -380,10 +419,14 @@ impl NetworkConfig { | |||||||
|                         None => continue, |                         None => continue, | ||||||
|                     }; |                     }; | ||||||
|  |  | ||||||
|                     if last_entry_was_comment {  writeln!(w)?; } |                     if last_entry_was_comment { | ||||||
|  |                         writeln!(w)?; | ||||||
|  |                     } | ||||||
|                     last_entry_was_comment = false; |                     last_entry_was_comment = false; | ||||||
|  |  | ||||||
|                     if done.contains(name) { continue; } |                     if done.contains(name) { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|                     done.insert(name); |                     done.insert(name); | ||||||
|  |  | ||||||
|                     write_iface(interface, w)?; |                     write_iface(interface, w)?; | ||||||
| @ -392,7 +435,9 @@ impl NetworkConfig { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         for (name, interface) in &self.interfaces { |         for (name, interface) in &self.interfaces { | ||||||
|             if done.contains(name) { continue; } |             if done.contains(name) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|             write_iface(interface, w)?; |             write_iface(interface, w)?; | ||||||
|         } |         } | ||||||
|         Ok(()) |         Ok(()) | ||||||
| @ -407,15 +452,16 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | |||||||
|     open_backup_lockfile(NETWORK_LOCKFILE, None, true) |     open_backup_lockfile(NETWORK_LOCKFILE, None, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> { | pub fn config() -> Result<(NetworkConfig, [u8; 32]), Error> { | ||||||
|  |     let content = | ||||||
|     let content = match proxmox_sys::fs::file_get_optional_contents(NETWORK_INTERFACES_NEW_FILENAME)? { |         match proxmox_sys::fs::file_get_optional_contents(NETWORK_INTERFACES_NEW_FILENAME)? { | ||||||
|         Some(content) => content, |             Some(content) => content, | ||||||
|         None => { |             None => { | ||||||
|             let content = proxmox_sys::fs::file_get_optional_contents(NETWORK_INTERFACES_FILENAME)?; |                 let content = | ||||||
|             content.unwrap_or_default() |                     proxmox_sys::fs::file_get_optional_contents(NETWORK_INTERFACES_FILENAME)?; | ||||||
|         } |                 content.unwrap_or_default() | ||||||
|     }; |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|     let digest = openssl::sha::sha256(&content); |     let digest = openssl::sha::sha256(&content); | ||||||
|  |  | ||||||
| @ -427,7 +473,6 @@ pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> { | |||||||
| } | } | ||||||
|  |  | ||||||
| pub fn changes() -> Result<String, Error> { | pub fn changes() -> Result<String, Error> { | ||||||
|  |  | ||||||
|     if !std::path::Path::new(NETWORK_INTERFACES_NEW_FILENAME).exists() { |     if !std::path::Path::new(NETWORK_INTERFACES_NEW_FILENAME).exists() { | ||||||
|         return Ok(String::new()); |         return Ok(String::new()); | ||||||
|     } |     } | ||||||
| @ -436,7 +481,6 @@ pub fn changes() -> Result<String, Error> { | |||||||
| } | } | ||||||
|  |  | ||||||
| pub fn save_config(config: &NetworkConfig) -> Result<(), Error> { | pub fn save_config(config: &NetworkConfig) -> Result<(), Error> { | ||||||
|  |  | ||||||
|     let mut raw = Vec::new(); |     let mut raw = Vec::new(); | ||||||
|     config.write_config(&mut raw)?; |     config.write_config(&mut raw)?; | ||||||
|  |  | ||||||
| @ -461,7 +505,6 @@ pub fn complete_interface_name(_arg: &str, _param: &HashMap<String, String>) -> | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| pub fn complete_port_list(arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | pub fn complete_port_list(arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||||
|     let mut ports = Vec::new(); |     let mut ports = Vec::new(); | ||||||
|     match config() { |     match config() { | ||||||
| @ -476,20 +519,26 @@ pub fn complete_port_list(arg: &str, _param: &HashMap<String, String>) -> Vec<St | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let arg = arg.trim(); |     let arg = arg.trim(); | ||||||
|     let prefix = if let Some(idx) = arg.rfind(',') { &arg[..idx+1] } else { "" }; |     let prefix = if let Some(idx) = arg.rfind(',') { | ||||||
|     ports.iter().map(|port| format!("{}{}", prefix, port)).collect() |         &arg[..idx + 1] | ||||||
|  |     } else { | ||||||
|  |         "" | ||||||
|  |     }; | ||||||
|  |     ports | ||||||
|  |         .iter() | ||||||
|  |         .map(|port| format!("{}{}", prefix, port)) | ||||||
|  |         .collect() | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod test { | mod test { | ||||||
|  |  | ||||||
|     use anyhow::{Error}; |     use anyhow::Error; | ||||||
|  |  | ||||||
|     use super::*; |     use super::*; | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_network_config_create_lo_1() -> Result<(), Error> { |     fn test_network_config_create_lo_1() -> Result<(), Error> { | ||||||
|  |  | ||||||
|         let input = ""; |         let input = ""; | ||||||
|  |  | ||||||
|         let mut parser = NetworkParser::new(input.as_bytes()); |         let mut parser = NetworkParser::new(input.as_bytes()); | ||||||
| @ -515,7 +564,6 @@ mod test { | |||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_network_config_create_lo_2() -> Result<(), Error> { |     fn test_network_config_create_lo_2() -> Result<(), Error> { | ||||||
|  |  | ||||||
|         let input = "#c1\n\n#c2\n\niface test inet manual\n"; |         let input = "#c1\n\n#c2\n\niface test inet manual\n"; | ||||||
|  |  | ||||||
|         let mut parser = NetworkParser::new(input.as_bytes()); |         let mut parser = NetworkParser::new(input.as_bytes()); | ||||||
|  | |||||||
| @ -1,15 +1,18 @@ | |||||||
| use std::io::{BufRead}; |  | ||||||
| use std::iter::{Peekable, Iterator}; |  | ||||||
| use std::collections::{HashMap, HashSet}; | use std::collections::{HashMap, HashSet}; | ||||||
|  | use std::io::BufRead; | ||||||
|  | use std::iter::{Iterator, Peekable}; | ||||||
|  |  | ||||||
| use anyhow::{Error, bail, format_err}; | use anyhow::{bail, format_err, Error}; | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use regex::Regex; | use regex::Regex; | ||||||
|  |  | ||||||
| use super::helper::*; | use super::helper::*; | ||||||
| use super::lexer::*; | use super::lexer::*; | ||||||
|  |  | ||||||
| use super::{NetworkConfig, NetworkOrderEntry, Interface, NetworkConfigMethod, NetworkInterfaceType, bond_mode_from_str, bond_xmit_hash_policy_from_str}; | use super::{ | ||||||
|  |     bond_mode_from_str, bond_xmit_hash_policy_from_str, Interface, NetworkConfig, | ||||||
|  |     NetworkConfigMethod, NetworkInterfaceType, NetworkOrderEntry, | ||||||
|  | }; | ||||||
|  |  | ||||||
| fn set_method_v4(iface: &mut Interface, method: NetworkConfigMethod) -> Result<(), Error> { | fn set_method_v4(iface: &mut Interface, method: NetworkConfigMethod) -> Result<(), Error> { | ||||||
|     if iface.method.is_none() { |     if iface.method.is_none() { | ||||||
| @ -65,11 +68,18 @@ fn set_gateway_v6(iface: &mut Interface, gateway: String) -> Result<(), Error> { | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
| fn set_interface_type(iface: &mut Interface, interface_type: NetworkInterfaceType) -> Result<(), Error> { | fn set_interface_type( | ||||||
|  |     iface: &mut Interface, | ||||||
|  |     interface_type: NetworkInterfaceType, | ||||||
|  | ) -> Result<(), Error> { | ||||||
|     if iface.interface_type == NetworkInterfaceType::Unknown { |     if iface.interface_type == NetworkInterfaceType::Unknown { | ||||||
|         iface.interface_type = interface_type; |         iface.interface_type = interface_type; | ||||||
|     } else if iface.interface_type != interface_type { |     } else if iface.interface_type != interface_type { | ||||||
|         bail!("interface type already defined - cannot change from {:?} to {:?}", iface.interface_type, interface_type); |         bail!( | ||||||
|  |             "interface type already defined - cannot change from {:?} to {:?}", | ||||||
|  |             iface.interface_type, | ||||||
|  |             interface_type | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
| @ -79,8 +89,7 @@ pub struct NetworkParser<R: BufRead> { | |||||||
|     line_nr: usize, |     line_nr: usize, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl <R: BufRead> NetworkParser<R> { | impl<R: BufRead> NetworkParser<R> { | ||||||
|  |  | ||||||
|     pub fn new(reader: R) -> Self { |     pub fn new(reader: R) -> Self { | ||||||
|         let input = Lexer::new(reader).peekable(); |         let input = Lexer::new(reader).peekable(); | ||||||
|         Self { input, line_nr: 1 } |         Self { input, line_nr: 1 } | ||||||
| @ -91,9 +100,7 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|             Some(Err(err)) => { |             Some(Err(err)) => { | ||||||
|                 bail!("input error - {}", err); |                 bail!("input error - {}", err); | ||||||
|             } |             } | ||||||
|             Some(Ok((token, _))) => { |             Some(Ok((token, _))) => Ok(*token), | ||||||
|                 Ok(*token) |  | ||||||
|             } |  | ||||||
|             None => { |             None => { | ||||||
|                 bail!("got unexpected end of stream (inside peek)"); |                 bail!("got unexpected end of stream (inside peek)"); | ||||||
|             } |             } | ||||||
| @ -106,7 +113,9 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|                 bail!("input error - {}", err); |                 bail!("input error - {}", err); | ||||||
|             } |             } | ||||||
|             Some(Ok((token, text))) => { |             Some(Ok((token, text))) => { | ||||||
|                 if token == Token::Newline { self.line_nr += 1; } |                 if token == Token::Newline { | ||||||
|  |                     self.line_nr += 1; | ||||||
|  |                 } | ||||||
|                 Ok((token, text)) |                 Ok((token, text)) | ||||||
|             } |             } | ||||||
|             None => { |             None => { | ||||||
| @ -136,14 +145,13 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|         loop { |         loop { | ||||||
|             match self.next()? { |             match self.next()? { | ||||||
|                 (Token::Text, iface) => { |                 (Token::Text, iface) => { | ||||||
|                      auto_flag.insert(iface.to_string()); |                     auto_flag.insert(iface.to_string()); | ||||||
|                 } |                 } | ||||||
|                 (Token::Newline, _) => break, |                 (Token::Newline, _) => break, | ||||||
|                 unexpected => { |                 unexpected => { | ||||||
|                     bail!("expected {:?}, got {:?}", Token::Text, unexpected); |                     bail!("expected {:?}, got {:?}", Token::Text, unexpected); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
| @ -153,7 +161,7 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|         self.eat(Token::Netmask)?; |         self.eat(Token::Netmask)?; | ||||||
|         let netmask = self.next_text()?; |         let netmask = self.next_text()?; | ||||||
|  |  | ||||||
|         let mask = if let Some(mask) = IPV4_MASK_HASH_LOCALNET.get(netmask.as_str())  { |         let mask = if let Some(mask) = IPV4_MASK_HASH_LOCALNET.get(netmask.as_str()) { | ||||||
|             *mask |             *mask | ||||||
|         } else { |         } else { | ||||||
|             match u8::from_str_radix(netmask.as_str(), 10) { |             match u8::from_str_radix(netmask.as_str(), 10) { | ||||||
| @ -236,7 +244,9 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|             match self.next()? { |             match self.next()? { | ||||||
|                 (Token::Newline, _) => return Ok(line), |                 (Token::Newline, _) => return Ok(line), | ||||||
|                 (_, text) => { |                 (_, text) => { | ||||||
|                     if !line.is_empty() { line.push(' '); } |                     if !line.is_empty() { | ||||||
|  |                         line.push(' '); | ||||||
|  |                     } | ||||||
|                     line.push_str(&text); |                     line.push_str(&text); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -255,7 +265,10 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|                         list.push(text); |                         list.push(text); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 _ => bail!("unable to parse interface list - unexpected token '{:?}'", token), |                 _ => bail!( | ||||||
|  |                     "unable to parse interface list - unexpected token '{:?}'", | ||||||
|  |                     token | ||||||
|  |                 ), | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @ -268,23 +281,28 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|         address_family_v4: bool, |         address_family_v4: bool, | ||||||
|         address_family_v6: bool, |         address_family_v6: bool, | ||||||
|     ) -> Result<(), Error> { |     ) -> Result<(), Error> { | ||||||
|  |  | ||||||
|         let mut netmask = None; |         let mut netmask = None; | ||||||
|         let mut address_list = Vec::new(); |         let mut address_list = Vec::new(); | ||||||
|  |  | ||||||
|         loop { |         loop { | ||||||
|             match self.peek()? { |             match self.peek()? { | ||||||
|                 Token::Attribute => { self.eat(Token::Attribute)?; }, |                 Token::Attribute => { | ||||||
|  |                     self.eat(Token::Attribute)?; | ||||||
|  |                 } | ||||||
|                 Token::Comment => { |                 Token::Comment => { | ||||||
|                     let comment = self.eat(Token::Comment)?; |                     let comment = self.eat(Token::Comment)?; | ||||||
|                     if !address_family_v4 && address_family_v6 { |                     if !address_family_v4 && address_family_v6 { | ||||||
|                         let mut comments = interface.comments6.take().unwrap_or_default(); |                         let mut comments = interface.comments6.take().unwrap_or_default(); | ||||||
|                         if !comments.is_empty() { comments.push('\n'); } |                         if !comments.is_empty() { | ||||||
|  |                             comments.push('\n'); | ||||||
|  |                         } | ||||||
|                         comments.push_str(&comment); |                         comments.push_str(&comment); | ||||||
|                         interface.comments6 = Some(comments); |                         interface.comments6 = Some(comments); | ||||||
|                     } else { |                     } else { | ||||||
|                         let mut comments = interface.comments.take().unwrap_or_default(); |                         let mut comments = interface.comments.take().unwrap_or_default(); | ||||||
|                         if !comments.is_empty() { comments.push('\n'); } |                         if !comments.is_empty() { | ||||||
|  |                             comments.push('\n'); | ||||||
|  |                         } | ||||||
|                         comments.push_str(&comment); |                         comments.push_str(&comment); | ||||||
|                         interface.comments = Some(comments); |                         interface.comments = Some(comments); | ||||||
|                     } |                     } | ||||||
| @ -343,7 +361,8 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|                     interface.bond_xmit_hash_policy = Some(policy); |                     interface.bond_xmit_hash_policy = Some(policy); | ||||||
|                     self.eat(Token::Newline)?; |                     self.eat(Token::Newline)?; | ||||||
|                 } |                 } | ||||||
|                 _ => { // parse addon attributes |                 _ => { | ||||||
|  |                     // parse addon attributes | ||||||
|                     let option = self.parse_to_eol()?; |                     let option = self.parse_to_eol()?; | ||||||
|                     if !option.is_empty() { |                     if !option.is_empty() { | ||||||
|                         if !address_family_v4 && address_family_v6 { |                         if !address_family_v4 && address_family_v6 { | ||||||
| @ -351,8 +370,8 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|                         } else { |                         } else { | ||||||
|                             interface.options.push(option); |                             interface.options.push(option); | ||||||
|                         } |                         } | ||||||
|                    }; |                     }; | ||||||
|                  }, |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @ -362,7 +381,7 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|                 bail!("unable to apply netmask to multiple addresses (please use cidr notation)"); |                 bail!("unable to apply netmask to multiple addresses (please use cidr notation)"); | ||||||
|             } else if address_list.len() == 1 { |             } else if address_list.len() == 1 { | ||||||
|                 let (mut cidr, mask, is_v6) = address_list.pop().unwrap(); |                 let (mut cidr, mask, is_v6) = address_list.pop().unwrap(); | ||||||
|                 if mask.is_some()  { |                 if mask.is_some() { | ||||||
|                     // address already has a mask  - ignore netmask |                     // address already has a mask  - ignore netmask | ||||||
|                 } else { |                 } else { | ||||||
|                     check_netmask(netmask, is_v6)?; |                     check_netmask(netmask, is_v6)?; | ||||||
| @ -449,12 +468,18 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn parse_interfaces(&mut self, existing_interfaces: Option<&HashMap<String, bool>>) -> Result<NetworkConfig, Error> { |     pub fn parse_interfaces( | ||||||
|  |         &mut self, | ||||||
|  |         existing_interfaces: Option<&HashMap<String, bool>>, | ||||||
|  |     ) -> Result<NetworkConfig, Error> { | ||||||
|         self._parse_interfaces(existing_interfaces) |         self._parse_interfaces(existing_interfaces) | ||||||
|             .map_err(|err| format_err!("line {}: {}", self.line_nr, err)) |             .map_err(|err| format_err!("line {}: {}", self.line_nr, err)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn _parse_interfaces(&mut self, existing_interfaces: Option<&HashMap<String, bool>>) -> Result<NetworkConfig, Error> { |     pub fn _parse_interfaces( | ||||||
|  |         &mut self, | ||||||
|  |         existing_interfaces: Option<&HashMap<String, bool>>, | ||||||
|  |     ) -> Result<NetworkConfig, Error> { | ||||||
|         let mut config = NetworkConfig::new(); |         let mut config = NetworkConfig::new(); | ||||||
|  |  | ||||||
|         let mut auto_flag: HashSet<String> = HashSet::new(); |         let mut auto_flag: HashSet<String> = HashSet::new(); | ||||||
| @ -494,31 +519,38 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         lazy_static!{ |         lazy_static! { | ||||||
|             static ref INTERFACE_ALIAS_REGEX: Regex = Regex::new(r"^\S+:\d+$").unwrap(); |             static ref INTERFACE_ALIAS_REGEX: Regex = Regex::new(r"^\S+:\d+$").unwrap(); | ||||||
|             static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+$").unwrap(); |             static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+$").unwrap(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if let Some(existing_interfaces) = existing_interfaces { |         if let Some(existing_interfaces) = existing_interfaces { | ||||||
|             for (iface, active) in existing_interfaces.iter()  { |             for (iface, active) in existing_interfaces.iter() { | ||||||
|                 if let Some(interface) = config.interfaces.get_mut(iface) { |                 if let Some(interface) = config.interfaces.get_mut(iface) { | ||||||
|                     interface.active = *active; |                     interface.active = *active; | ||||||
|                     if interface.interface_type == NetworkInterfaceType::Unknown && super::is_physical_nic(iface) { |                     if interface.interface_type == NetworkInterfaceType::Unknown | ||||||
|  |                         && super::is_physical_nic(iface) | ||||||
|  |                     { | ||||||
|                         interface.interface_type = NetworkInterfaceType::Eth; |                         interface.interface_type = NetworkInterfaceType::Eth; | ||||||
|                     } |                     } | ||||||
|                 } else if super::is_physical_nic(iface) { // also add all physical NICs |                 } else if super::is_physical_nic(iface) { | ||||||
|  |                     // also add all physical NICs | ||||||
|                     let mut interface = Interface::new(iface.clone()); |                     let mut interface = Interface::new(iface.clone()); | ||||||
|                     set_method_v4(&mut interface, NetworkConfigMethod::Manual)?; |                     set_method_v4(&mut interface, NetworkConfigMethod::Manual)?; | ||||||
|                     interface.interface_type = NetworkInterfaceType::Eth; |                     interface.interface_type = NetworkInterfaceType::Eth; | ||||||
|                     interface.active = *active; |                     interface.active = *active; | ||||||
|                     config.interfaces.insert(interface.name.clone(), interface); |                     config.interfaces.insert(interface.name.clone(), interface); | ||||||
|                     config.order.push(NetworkOrderEntry::Iface(iface.to_string())); |                     config | ||||||
|  |                         .order | ||||||
|  |                         .push(NetworkOrderEntry::Iface(iface.to_string())); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         for (name, interface) in config.interfaces.iter_mut() { |         for (name, interface) in config.interfaces.iter_mut() { | ||||||
|             if interface.interface_type != NetworkInterfaceType::Unknown { continue; } |             if interface.interface_type != NetworkInterfaceType::Unknown { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|             if name == "lo" { |             if name == "lo" { | ||||||
|                 interface.interface_type = NetworkInterfaceType::Loopback; |                 interface.interface_type = NetworkInterfaceType::Loopback; | ||||||
|                 continue; |                 continue; | ||||||
| @ -548,11 +580,14 @@ impl <R: BufRead> NetworkParser<R> { | |||||||
|             let mut new_order = Vec::new(); |             let mut new_order = Vec::new(); | ||||||
|             let mut added_lo = false; |             let mut added_lo = false; | ||||||
|             for entry in config.order { |             for entry in config.order { | ||||||
|                 if added_lo { new_order.push(entry); continue; } // copy the rest |                 if added_lo { | ||||||
|  |                     new_order.push(entry); | ||||||
|  |                     continue; | ||||||
|  |                 } // copy the rest | ||||||
|                 match entry { |                 match entry { | ||||||
|                     NetworkOrderEntry::Comment(_) => { |                     NetworkOrderEntry::Comment(_) => { | ||||||
|                         new_order.push(entry); |                         new_order.push(entry); | ||||||
|                      } |                     } | ||||||
|                     _ => { |                     _ => { | ||||||
|                         new_order.push(NetworkOrderEntry::Iface(String::from("lo"))); |                         new_order.push(NetworkOrderEntry::Iface(String::from("lo"))); | ||||||
|                         added_lo = true; |                         added_lo = true; | ||||||
|  | |||||||
| @ -20,7 +20,8 @@ fn init() -> SectionConfig { | |||||||
|         _ => unreachable!(), |         _ => unreachable!(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let plugin = SectionConfigPlugin::new("remote".to_string(), Some("name".to_string()), obj_schema); |     let plugin = | ||||||
|  |         SectionConfigPlugin::new("remote".to_string(), Some("name".to_string()), obj_schema); | ||||||
|     let mut config = SectionConfig::new(&REMOTE_ID_SCHEMA); |     let mut config = SectionConfig::new(&REMOTE_ID_SCHEMA); | ||||||
|     config.register_plugin(plugin); |     config.register_plugin(plugin); | ||||||
|  |  | ||||||
| @ -35,8 +36,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | |||||||
|     open_backup_lockfile(REMOTE_CFG_LOCKFILE, None, true) |     open_backup_lockfile(REMOTE_CFG_LOCKFILE, None, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(REMOTE_CFG_FILENAME)? |     let content = proxmox_sys::fs::file_read_optional_string(REMOTE_CFG_FILENAME)? | ||||||
|         .unwrap_or_else(|| "".to_string()); |         .unwrap_or_else(|| "".to_string()); | ||||||
|  |  | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ use lazy_static::lazy_static; | |||||||
| use proxmox_schema::{ApiType, Schema}; | use proxmox_schema::{ApiType, Schema}; | ||||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||||
|  |  | ||||||
| use pbs_api_types::{JOB_ID_SCHEMA, SyncJobConfig}; | use pbs_api_types::{SyncJobConfig, JOB_ID_SCHEMA}; | ||||||
|  |  | ||||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||||
|  |  | ||||||
| @ -14,7 +14,6 @@ lazy_static! { | |||||||
|     pub static ref CONFIG: SectionConfig = init(); |     pub static ref CONFIG: SectionConfig = init(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| fn init() -> SectionConfig { | fn init() -> SectionConfig { | ||||||
|     let obj_schema = match SyncJobConfig::API_SCHEMA { |     let obj_schema = match SyncJobConfig::API_SCHEMA { | ||||||
|         Schema::AllOf(ref allof_schema) => allof_schema, |         Schema::AllOf(ref allof_schema) => allof_schema, | ||||||
| @ -36,8 +35,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | |||||||
|     open_backup_lockfile(SYNC_CFG_LOCKFILE, None, true) |     open_backup_lockfile(SYNC_CFG_LOCKFILE, None, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(SYNC_CFG_FILENAME)? |     let content = proxmox_sys::fs::file_read_optional_string(SYNC_CFG_FILENAME)? | ||||||
|         .unwrap_or_else(|| "".to_string()); |         .unwrap_or_else(|| "".to_string()); | ||||||
|  |  | ||||||
|  | |||||||
| @ -15,20 +15,17 @@ use std::collections::HashMap; | |||||||
| use anyhow::{bail, Error}; | use anyhow::{bail, Error}; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
|  |  | ||||||
| use proxmox_sys::fs::file_read_optional_string; |  | ||||||
| use pbs_api_types::Fingerprint; | use pbs_api_types::Fingerprint; | ||||||
|  | use proxmox_sys::fs::file_read_optional_string; | ||||||
|  |  | ||||||
| use crate::key_config::KeyConfig; | use crate::key_config::KeyConfig; | ||||||
| use crate::{open_backup_lockfile, replace_secret_config, replace_backup_config}; | use crate::{open_backup_lockfile, replace_backup_config, replace_secret_config}; | ||||||
|  |  | ||||||
| mod hex_key { | mod hex_key { | ||||||
|     use serde::{self, Deserialize, Serializer, Deserializer}; |  | ||||||
|     use hex::FromHex; |     use hex::FromHex; | ||||||
|      |     use serde::{self, Deserialize, Deserializer, Serializer}; | ||||||
|     pub fn serialize<S>( |  | ||||||
|         csum: &[u8; 32], |     pub fn serialize<S>(csum: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error> | ||||||
|         serializer: S, |  | ||||||
|     ) -> Result<S::Ok, S::Error> |  | ||||||
|     where |     where | ||||||
|         S: Serializer, |         S: Serializer, | ||||||
|     { |     { | ||||||
| @ -36,9 +33,7 @@ mod hex_key { | |||||||
|         serializer.serialize_str(&s) |         serializer.serialize_str(&s) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn deserialize<'de, D>( |     pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error> | ||||||
|         deserializer: D, |  | ||||||
|     ) -> Result<[u8; 32], D::Error> |  | ||||||
|     where |     where | ||||||
|         D: Deserializer<'de>, |         D: Deserializer<'de>, | ||||||
|     { |     { | ||||||
| @ -68,8 +63,7 @@ pub const TAPE_KEY_CONFIG_FILENAME: &str = "/etc/proxmox-backup/tape-encryption- | |||||||
| pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck"; | pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck"; | ||||||
|  |  | ||||||
| /// Load tape encryption keys (plain, unprotected keys) | /// Load tape encryption keys (plain, unprotected keys) | ||||||
| pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>,  [u8;32]), Error> { | pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = file_read_optional_string(TAPE_KEYS_FILENAME)?; |     let content = file_read_optional_string(TAPE_KEYS_FILENAME)?; | ||||||
|     let content = content.unwrap_or_else(|| String::from("[]")); |     let content = content.unwrap_or_else(|| String::from("[]")); | ||||||
|  |  | ||||||
| @ -99,8 +93,7 @@ pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>,  [u8;32]) | |||||||
| } | } | ||||||
|  |  | ||||||
| /// Load tape encryption key configurations (password protected keys) | /// Load tape encryption key configurations (password protected keys) | ||||||
| pub fn load_key_configs() -> Result<(HashMap<Fingerprint, KeyConfig>,  [u8;32]), Error> { | pub fn load_key_configs() -> Result<(HashMap<Fingerprint, KeyConfig>, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = file_read_optional_string(TAPE_KEY_CONFIG_FILENAME)?; |     let content = file_read_optional_string(TAPE_KEY_CONFIG_FILENAME)?; | ||||||
|     let content = content.unwrap_or_else(|| String::from("[]")); |     let content = content.unwrap_or_else(|| String::from("[]")); | ||||||
|  |  | ||||||
| @ -128,7 +121,6 @@ pub fn load_key_configs() -> Result<(HashMap<Fingerprint, KeyConfig>,  [u8;32]), | |||||||
| /// | /// | ||||||
| /// The file is only accessible by user root (mode 0600). | /// The file is only accessible by user root (mode 0600). | ||||||
| pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Error> { | pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Error> { | ||||||
|  |  | ||||||
|     let mut list = Vec::new(); |     let mut list = Vec::new(); | ||||||
|  |  | ||||||
|     for (_fp, item) in map { |     for (_fp, item) in map { | ||||||
| @ -141,7 +133,6 @@ pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Err | |||||||
|  |  | ||||||
| /// Store tape encryption key configurations (password protected keys) | /// Store tape encryption key configurations (password protected keys) | ||||||
| pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Error> { | pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Error> { | ||||||
|  |  | ||||||
|     let mut list = Vec::new(); |     let mut list = Vec::new(); | ||||||
|  |  | ||||||
|     for (_fp, item) in map { |     for (_fp, item) in map { | ||||||
| @ -155,8 +146,7 @@ pub fn save_key_configs(map: HashMap<Fingerprint, KeyConfig>) -> Result<(), Erro | |||||||
| /// Insert a new key | /// Insert a new key | ||||||
| /// | /// | ||||||
| /// Get the lock, load both files, insert the new key, store files. | /// Get the lock, load both files, insert the new key, store files. | ||||||
| pub fn insert_key(key: [u8;32], key_config: KeyConfig, force: bool) -> Result<(), Error> { | pub fn insert_key(key: [u8; 32], key_config: KeyConfig, force: bool) -> Result<(), Error> { | ||||||
|  |  | ||||||
|     let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; |     let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; | ||||||
|  |  | ||||||
|     let (mut key_map, _) = load_keys()?; |     let (mut key_map, _) = load_keys()?; | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| use anyhow::{Error}; | use anyhow::Error; | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
|  |  | ||||||
| use proxmox_schema::{Schema, ApiType}; | use proxmox_schema::{ApiType, Schema}; | ||||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||||
|  |  | ||||||
| use pbs_api_types::{TapeBackupJobConfig, JOB_ID_SCHEMA}; | use pbs_api_types::{TapeBackupJobConfig, JOB_ID_SCHEMA}; | ||||||
| @ -19,7 +19,8 @@ fn init() -> SectionConfig { | |||||||
|         _ => unreachable!(), |         _ => unreachable!(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let plugin = SectionConfigPlugin::new("backup".to_string(), Some(String::from("id")), obj_schema); |     let plugin = | ||||||
|  |         SectionConfigPlugin::new("backup".to_string(), Some(String::from("id")), obj_schema); | ||||||
|     let mut config = SectionConfig::new(&JOB_ID_SCHEMA); |     let mut config = SectionConfig::new(&JOB_ID_SCHEMA); | ||||||
|     config.register_plugin(plugin); |     config.register_plugin(plugin); | ||||||
|  |  | ||||||
| @ -31,11 +32,10 @@ pub const TAPE_JOB_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.tape-job.lck"; | |||||||
|  |  | ||||||
| /// Get exclusive lock | /// Get exclusive lock | ||||||
| pub fn lock() -> Result<BackupLockGuard, Error> { | pub fn lock() -> Result<BackupLockGuard, Error> { | ||||||
|     open_backup_lockfile( TAPE_JOB_CFG_LOCKFILE, None, true) |     open_backup_lockfile(TAPE_JOB_CFG_LOCKFILE, None, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(TAPE_JOB_CFG_FILENAME)? |     let content = proxmox_sys::fs::file_read_optional_string(TAPE_JOB_CFG_FILENAME)? | ||||||
|         .unwrap_or_else(|| "".to_string()); |         .unwrap_or_else(|| "".to_string()); | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
|  |  | ||||||
| use anyhow::{bail, format_err, Error}; | use anyhow::{bail, format_err, Error}; | ||||||
| use serde::{Serialize, Deserialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use serde_json::{from_value, Value}; | use serde_json::{from_value, Value}; | ||||||
|  |  | ||||||
| use proxmox_sys::fs::CreateOptions; | use proxmox_sys::fs::CreateOptions; | ||||||
| @ -14,7 +14,7 @@ const LOCK_FILE: &str = pbs_buildcfg::configdir!("/token.shadow.lock"); | |||||||
| const CONF_FILE: &str = pbs_buildcfg::configdir!("/token.shadow"); | const CONF_FILE: &str = pbs_buildcfg::configdir!("/token.shadow"); | ||||||
|  |  | ||||||
| #[derive(Serialize, Deserialize)] | #[derive(Serialize, Deserialize)] | ||||||
| #[serde(rename_all="kebab-case")] | #[serde(rename_all = "kebab-case")] | ||||||
| /// ApiToken id / secret pair | /// ApiToken id / secret pair | ||||||
| pub struct ApiTokenSecret { | pub struct ApiTokenSecret { | ||||||
|     pub tokenid: Authid, |     pub tokenid: Authid, | ||||||
| @ -48,7 +48,6 @@ fn write_file(data: HashMap<Authid, String>) -> Result<(), Error> { | |||||||
|     proxmox_sys::fs::replace_file(CONF_FILE, &json, options, true) |     proxmox_sys::fs::replace_file(CONF_FILE, &json, options, true) | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| /// Verifies that an entry for given tokenid / API token secret exists | /// Verifies that an entry for given tokenid / API token secret exists | ||||||
| pub fn verify_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> { | pub fn verify_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> { | ||||||
|     if !tokenid.is_token() { |     if !tokenid.is_token() { | ||||||
| @ -57,9 +56,7 @@ pub fn verify_secret(tokenid: &Authid, secret: &str) -> Result<(), Error> { | |||||||
|  |  | ||||||
|     let data = read_file()?; |     let data = read_file()?; | ||||||
|     match data.get(tokenid) { |     match data.get(tokenid) { | ||||||
|         Some(hashed_secret) => { |         Some(hashed_secret) => proxmox_sys::crypt::verify_crypt_pw(secret, hashed_secret), | ||||||
|             proxmox_sys::crypt::verify_crypt_pw(secret, hashed_secret) |  | ||||||
|         }, |  | ||||||
|         None => bail!("invalid API token"), |         None => bail!("invalid API token"), | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -42,8 +42,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// Read and parse the configuration file | /// Read and parse the configuration file | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(TRAFFIC_CONTROL_CFG_FILENAME)? |     let content = proxmox_sys::fs::file_read_optional_string(TRAFFIC_CONTROL_CFG_FILENAME)? | ||||||
|         .unwrap_or_else(|| "".to_string()); |         .unwrap_or_else(|| "".to_string()); | ||||||
|  |  | ||||||
| @ -65,7 +64,6 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| // shell completion helper | // shell completion helper | ||||||
| pub fn complete_traffic_control_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | pub fn complete_traffic_control_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||||
|     match config() { |     match config() { | ||||||
| @ -93,5 +91,4 @@ mod test { | |||||||
|  |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,9 +7,7 @@ use lazy_static::lazy_static; | |||||||
| use proxmox_schema::*; | use proxmox_schema::*; | ||||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||||
|  |  | ||||||
| use pbs_api_types::{ | use pbs_api_types::{ApiToken, Authid, User, Userid}; | ||||||
|     Authid, Userid, ApiToken, User, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| use crate::ConfigVersionCache; | use crate::ConfigVersionCache; | ||||||
|  |  | ||||||
| @ -26,14 +24,19 @@ fn init() -> SectionConfig { | |||||||
|         Schema::Object(ref user_schema) => user_schema, |         Schema::Object(ref user_schema) => user_schema, | ||||||
|         _ => unreachable!(), |         _ => unreachable!(), | ||||||
|     }; |     }; | ||||||
|     let user_plugin = SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), user_schema); |     let user_plugin = | ||||||
|  |         SectionConfigPlugin::new("user".to_string(), Some("userid".to_string()), user_schema); | ||||||
|     config.register_plugin(user_plugin); |     config.register_plugin(user_plugin); | ||||||
|  |  | ||||||
|     let token_schema = match ApiToken::API_SCHEMA { |     let token_schema = match ApiToken::API_SCHEMA { | ||||||
|         Schema::Object(ref token_schema) => token_schema, |         Schema::Object(ref token_schema) => token_schema, | ||||||
|         _ => unreachable!(), |         _ => unreachable!(), | ||||||
|     }; |     }; | ||||||
|     let token_plugin = SectionConfigPlugin::new("token".to_string(), Some("tokenid".to_string()), token_schema); |     let token_plugin = SectionConfigPlugin::new( | ||||||
|  |         "token".to_string(), | ||||||
|  |         Some("tokenid".to_string()), | ||||||
|  |         token_schema, | ||||||
|  |     ); | ||||||
|     config.register_plugin(token_plugin); |     config.register_plugin(token_plugin); | ||||||
|  |  | ||||||
|     config |     config | ||||||
| @ -47,8 +50,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | |||||||
|     open_backup_lockfile(USER_CFG_LOCKFILE, None, true) |     open_backup_lockfile(USER_CFG_LOCKFILE, None, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(USER_CFG_FILENAME)? |     let content = proxmox_sys::fs::file_read_optional_string(USER_CFG_FILENAME)? | ||||||
|         .unwrap_or_else(|| "".to_string()); |         .unwrap_or_else(|| "".to_string()); | ||||||
|  |  | ||||||
| @ -72,7 +74,6 @@ pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | |||||||
| } | } | ||||||
|  |  | ||||||
| pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> { | pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> { | ||||||
|  |  | ||||||
|     struct ConfigCache { |     struct ConfigCache { | ||||||
|         data: Option<Arc<SectionConfigData>>, |         data: Option<Arc<SectionConfigData>>, | ||||||
|         last_mtime: i64, |         last_mtime: i64, | ||||||
| @ -80,8 +81,11 @@ pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     lazy_static! { |     lazy_static! { | ||||||
|         static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new( |         static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(ConfigCache { | ||||||
|             ConfigCache { data: None, last_mtime: 0, last_mtime_nsec: 0 }); |             data: None, | ||||||
|  |             last_mtime: 0, | ||||||
|  |             last_mtime_nsec: 0 | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let stat = match nix::sys::stat::stat(USER_CFG_FILENAME) { |     let stat = match nix::sys::stat::stat(USER_CFG_FILENAME) { | ||||||
| @ -90,11 +94,13 @@ pub fn cached_config() -> Result<Arc<SectionConfigData>, Error> { | |||||||
|         Err(err) => bail!("unable to stat '{}' - {}", USER_CFG_FILENAME, err), |         Err(err) => bail!("unable to stat '{}' - {}", USER_CFG_FILENAME, err), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     { // limit scope |     { | ||||||
|  |         // limit scope | ||||||
|         let cache = CACHED_CONFIG.read().unwrap(); |         let cache = CACHED_CONFIG.read().unwrap(); | ||||||
|         if let Some(ref config) = cache.data { |         if let Some(ref config) = cache.data { | ||||||
|             if let Some(stat) = stat { |             if let Some(stat) = stat { | ||||||
|                 if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec { |                 if stat.st_mtime == cache.last_mtime && stat.st_mtime_nsec == cache.last_mtime_nsec | ||||||
|  |                 { | ||||||
|                     return Ok(config.clone()); |                     return Ok(config.clone()); | ||||||
|                 } |                 } | ||||||
|             } else if cache.last_mtime == 0 && cache.last_mtime_nsec == 0 { |             } else if cache.last_mtime == 0 && cache.last_mtime_nsec == 0 { | ||||||
| @ -130,26 +136,27 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { | |||||||
|  |  | ||||||
| /// Only exposed for testing | /// Only exposed for testing | ||||||
| #[doc(hidden)] | #[doc(hidden)] | ||||||
| pub fn test_cfg_from_str(raw: &str) -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn test_cfg_from_str(raw: &str) -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|     let cfg = init(); |     let cfg = init(); | ||||||
|     let parsed = cfg.parse("test_user_cfg", raw)?; |     let parsed = cfg.parse("test_user_cfg", raw)?; | ||||||
|  |  | ||||||
|     Ok((parsed, [0;32])) |     Ok((parsed, [0; 32])) | ||||||
| } | } | ||||||
|  |  | ||||||
| // shell completion helper | // shell completion helper | ||||||
| pub fn complete_userid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | pub fn complete_userid(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { | ||||||
|     match config() { |     match config() { | ||||||
|         Ok((data, _digest)) => { |         Ok((data, _digest)) => data | ||||||
|             data.sections.iter() |             .sections | ||||||
|                 .filter_map(|(id, (section_type, _))| { |             .iter() | ||||||
|                     if section_type == "user" { |             .filter_map(|(id, (section_type, _))| { | ||||||
|                         Some(id.to_string()) |                 if section_type == "user" { | ||||||
|                     } else { |                     Some(id.to_string()) | ||||||
|                         None |                 } else { | ||||||
|                     } |                     None | ||||||
|                 }).collect() |                 } | ||||||
|         }, |             }) | ||||||
|  |             .collect(), | ||||||
|         Err(_) => return vec![], |         Err(_) => return vec![], | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -174,21 +181,20 @@ pub fn complete_token_name(_arg: &str, param: &HashMap<String, String>) -> Vec<S | |||||||
|             let user = data.lookup::<User>("user", userid); |             let user = data.lookup::<User>("user", userid); | ||||||
|             let tokens = data.convert_to_typed_array("token"); |             let tokens = data.convert_to_typed_array("token"); | ||||||
|             match (user, tokens) { |             match (user, tokens) { | ||||||
|                 (Ok(_), Ok(tokens)) => { |                 (Ok(_), Ok(tokens)) => tokens | ||||||
|                     tokens |                     .into_iter() | ||||||
|                         .into_iter() |                     .filter_map(|token: ApiToken| { | ||||||
|                         .filter_map(|token: ApiToken| { |                         let tokenid = token.tokenid; | ||||||
|                             let tokenid = token.tokenid; |                         if tokenid.is_token() && tokenid.user() == userid { | ||||||
|                             if tokenid.is_token() && tokenid.user() == userid { |                             Some(tokenid.tokenname().unwrap().as_str().to_string()) | ||||||
|                                 Some(tokenid.tokenname().unwrap().as_str().to_string()) |                         } else { | ||||||
|                             } else { |                             None | ||||||
|                                 None |                         } | ||||||
|                             } |                     }) | ||||||
|                         }).collect() |                     .collect(), | ||||||
|                 }, |  | ||||||
|                 _ => vec![], |                 _ => vec![], | ||||||
|             } |             } | ||||||
|         }, |         } | ||||||
|         None => vec![], |         None => vec![], | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ use lazy_static::lazy_static; | |||||||
| use proxmox_schema::*; | use proxmox_schema::*; | ||||||
| use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin}; | ||||||
|  |  | ||||||
| use pbs_api_types::{JOB_ID_SCHEMA, VerificationJobConfig}; | use pbs_api_types::{VerificationJobConfig, JOB_ID_SCHEMA}; | ||||||
|  |  | ||||||
| use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard}; | ||||||
|  |  | ||||||
| @ -20,7 +20,11 @@ fn init() -> SectionConfig { | |||||||
|         _ => unreachable!(), |         _ => unreachable!(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let plugin = SectionConfigPlugin::new("verification".to_string(), Some(String::from("id")), obj_schema); |     let plugin = SectionConfigPlugin::new( | ||||||
|  |         "verification".to_string(), | ||||||
|  |         Some(String::from("id")), | ||||||
|  |         obj_schema, | ||||||
|  |     ); | ||||||
|     let mut config = SectionConfig::new(&JOB_ID_SCHEMA); |     let mut config = SectionConfig::new(&JOB_ID_SCHEMA); | ||||||
|     config.register_plugin(plugin); |     config.register_plugin(plugin); | ||||||
|  |  | ||||||
| @ -35,8 +39,7 @@ pub fn lock_config() -> Result<BackupLockGuard, Error> { | |||||||
|     open_backup_lockfile(VERIFICATION_CFG_LOCKFILE, None, true) |     open_backup_lockfile(VERIFICATION_CFG_LOCKFILE, None, true) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> { | pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> { | ||||||
|  |  | ||||||
|     let content = proxmox_sys::fs::file_read_optional_string(VERIFICATION_CFG_FILENAME)?; |     let content = proxmox_sys::fs::file_read_optional_string(VERIFICATION_CFG_FILENAME)?; | ||||||
|     let content = content.unwrap_or_else(String::new); |     let content = content.unwrap_or_else(String::new); | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user