diff options
Diffstat (limited to 'crates/directory/src/core/config.rs')
-rw-r--r-- | crates/directory/src/core/config.rs | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/crates/directory/src/core/config.rs b/crates/directory/src/core/config.rs new file mode 100644 index 00000000..74c09ee5 --- /dev/null +++ b/crates/directory/src/core/config.rs @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2023 Stalwart Labs Ltd. + * + * This file is part of Stalwart Mail Server. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * in the LICENSE file at the top-level directory of this distribution. + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * You can be released from the requirements of the AGPLv3 license by + * purchasing a commercial license. Please contact licensing@stalw.art + * for more details. +*/ + +use deadpool::{ + managed::{Manager, Pool}, + Runtime, +}; +use regex::Regex; +use std::{sync::Arc, time::Duration}; +use store::Stores; +use utils::config::{ + utils::{AsKey, ParseValue}, + Config, +}; + +use ahash::AHashMap; + +use crate::{ + backend::{ + imap::ImapDirectory, ldap::LdapDirectory, memory::MemoryDirectory, smtp::SmtpDirectory, + sql::SqlDirectory, + }, + AddressMapping, Directories, Directory, DirectoryInner, +}; + +use super::cache::CachedDirectory; + +pub trait ConfigDirectory { + fn parse_directory( + &self, + stores: &Stores, + id_store: Option<&str>, + ) -> utils::config::Result<Directories>; +} + +impl ConfigDirectory for Config { + fn parse_directory( + &self, + stores: &Stores, + id_store: Option<&str>, + ) -> utils::config::Result<Directories> { + let mut config = Directories { + directories: AHashMap::new(), + }; + let id_store = id_store.and_then(|id| stores.stores.get(id).cloned()); + + for id in self.sub_keys("directory") { + // Parse directory + let protocol = self.value_require(("directory", id, "type"))?; + let prefix = ("directory", id); + let store = match protocol { + "internal" => DirectoryInner::Internal( + stores + .stores + .get(self.value_require(("directory", id, "store"))?) + .cloned() + .ok_or_else(|| { + format!( + "Failed to find store {:?} for directory {:?}.", + self.value_require(("directory", id, "store")).unwrap(), + id + ) + })?, + ), + "ldap" => DirectoryInner::Ldap(LdapDirectory::from_config( + self, + prefix, + id_store.clone(), + )?), + "sql" => DirectoryInner::Sql(SqlDirectory::from_config( + self, + prefix, + stores, + id_store.clone(), + )?), + "imap" => DirectoryInner::Imap(ImapDirectory::from_config(self, prefix)?), + "smtp" => DirectoryInner::Smtp(SmtpDirectory::from_config(self, prefix, false)?), + "lmtp" => DirectoryInner::Smtp(SmtpDirectory::from_config(self, prefix, true)?), + "memory" => DirectoryInner::Memory(MemoryDirectory::from_config( + self, + prefix, + id_store.clone(), + )?), + unknown => { + return Err(format!("Unknown directory type: {unknown:?}")); + } + }; + + config.directories.insert( + id.to_string(), + Arc::new(Directory { + store, + catch_all: AddressMapping::from_config( + self, + ("directory", id, "options.catch-all"), + )?, + subaddressing: AddressMapping::from_config( + self, + ("directory", id, "options.subaddressing"), + )?, + cache: CachedDirectory::try_from_config(self, ("directory", id))?, + }), + ); + } + + Ok(config) + } +} + +impl AddressMapping { + pub fn from_config(config: &Config, key: impl AsKey) -> utils::config::Result<Self> { + let key = key.as_key(); + if let Some(value) = config.value(key.as_str()) { + match value { + "true" => Ok(AddressMapping::Enable), + "false" => Ok(AddressMapping::Disable), + _ => Err(format!( + "Invalid value for address mapping {key:?}: {value:?}", + )), + } + } else if let Some(regex) = config.value((key.as_str(), "map")) { + Ok(AddressMapping::Custom { + regex: Regex::new(regex).map_err(|err| { + format!( + "Failed to compile regular expression {:?} for key {:?}: {}.", + regex, + (&key, "map").as_key(), + err + ) + })?, + mapping: config.property_require((key.as_str(), "to"))?, + }) + } else { + Ok(AddressMapping::Disable) + } + } +} + +pub(crate) fn build_pool<M: Manager>( + config: &Config, + prefix: &str, + manager: M, +) -> utils::config::Result<Pool<M>> { + Pool::builder(manager) + .runtime(Runtime::Tokio1) + .max_size(config.property_or_static((prefix, "pool.max-connections"), "10")?) + .create_timeout( + config + .property_or_static::<Duration>((prefix, "pool.timeout.create"), "30s")? + .into(), + ) + .wait_timeout(config.property_or_static((prefix, "pool.timeout.wait"), "30s")?) + .recycle_timeout(config.property_or_static((prefix, "pool.timeout.recycle"), "30s")?) + .build() + .map_err(|err| { + format!( + "Failed to build pool for {prefix:?}: {err}", + prefix = prefix, + err = err + ) + }) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LookupType { + List, + Glob, + Regex, + Map, +} + +#[derive(Debug, Clone)] +pub struct LookupFormat { + pub lookup_type: LookupType, + pub comment: Option<String>, + pub separator: Option<String>, +} + +impl Default for LookupFormat { + fn default() -> Self { + Self { + lookup_type: LookupType::Glob, + comment: Default::default(), + separator: Default::default(), + } + } +} + +impl ParseValue for LookupType { + fn parse_value(key: impl AsKey, value: &str) -> utils::config::Result<Self> { + match value { + "list" => Ok(LookupType::List), + "glob" => Ok(LookupType::Glob), + "regex" => Ok(LookupType::Regex), + "map" => Ok(LookupType::Map), + _ => Err(format!( + "Invalid value for lookup type {key:?}: {value:?}", + key = key.as_key(), + value = value + )), + } + } +} |