stalwart_lib/common/src/config/
mod.rs1use self::{
8 imap::ImapConfig, jmap::settings::JmapConfig, scripts::Scripting, smtp::SmtpConfig,
9 storage::Storage,
10};
11use crate::common::GroupwareConfig;
12use crate::common::{
13 Core, Network, Security, auth::oauth::config::OAuthConfig, expr::*,
14 listener::tls::AcmeProviders, manager::config::ConfigManager,
15};
16use crate::directory::{Directories, Directory};
17use crate::store::{BlobBackend, BlobStore, InMemoryStore, SearchStore, Store, Stores};
18use crate::utils::config::{Config, utils::AsKey};
19use arc_swap::ArcSwap;
20use hyper::HeaderMap;
21use ring::signature::{EcdsaKeyPair, RsaKeyPair};
22use spamfilter::SpamFilterConfig;
23use std::sync::Arc;
24use telemetry::Metrics;
25
26pub mod groupware;
27pub mod imap;
28pub mod inner;
29pub mod jmap;
30pub mod network;
31pub mod scripts;
32pub mod server;
33pub mod smtp;
34pub mod spamfilter;
35pub mod storage;
36pub mod telemetry;
37
38pub(crate) const CONNECTION_VARS: &[u32; 9] = &[
39 V_LISTENER,
40 V_REMOTE_IP,
41 V_REMOTE_PORT,
42 V_LOCAL_IP,
43 V_LOCAL_PORT,
44 V_PROTOCOL,
45 V_TLS,
46 V_ASN,
47 V_COUNTRY,
48];
49
50impl Core {
51 pub async fn parse(
52 config: &mut Config,
53 mut stores: Stores,
54 config_manager: ConfigManager,
55 ) -> Self {
56 let mut data = config
57 .value_require("storage.data")
58 .map(|id| id.to_string())
59 .and_then(|id| {
60 if let Some(store) = stores.stores.get(&id) {
61 store.clone().into()
62 } else {
63 config.new_parse_error("storage.data", format!("Data store {id:?} not found"));
64 None
65 }
66 })
67 .unwrap_or_default();
68
69 #[cfg(not(feature = "enterprise"))]
70 let is_enterprise = false;
71
72 #[cfg(feature = "enterprise")]
76 let enterprise =
77 crate::common::enterprise::Enterprise::parse(config, &config_manager, &stores, &data)
78 .await;
79
80 #[cfg(feature = "enterprise")]
81 let is_enterprise = enterprise.is_some();
82
83 #[cfg(feature = "enterprise")]
84 if !is_enterprise {
85 if data.is_enterprise_store() {
86 config
87 .new_build_error("storage.data", "SQL read replicas is an Enterprise feature");
88 data = Store::None;
89 }
90 stores.disable_enterprise_only();
91 }
92 let mut blob = config
95 .value_require("storage.blob")
96 .map(|id| id.to_string())
97 .and_then(|id| {
98 if let Some(store) = stores.blob_stores.get(&id) {
99 store.clone().into()
100 } else {
101 config.new_parse_error("storage.blob", format!("Blob store {id:?} not found"));
102 None
103 }
104 })
105 .unwrap_or_default();
106 let mut lookup = config
107 .value_require("storage.lookup")
108 .map(|id| id.to_string())
109 .and_then(|id| {
110 if let Some(store) = stores.in_memory_stores.get(&id) {
111 store.clone().into()
112 } else {
113 config.new_parse_error(
114 "storage.lookup",
115 format!("In-memory store {id:?} not found"),
116 );
117 None
118 }
119 })
120 .unwrap_or_default();
121 let mut fts = config
122 .value_require("storage.fts")
123 .map(|id| id.to_string())
124 .and_then(|id| {
125 if let Some(store) = stores.search_stores.get(&id) {
126 store.clone().into()
127 } else {
128 config.new_parse_error(
129 "storage.fts",
130 format!("Full-text store {id:?} not found"),
131 );
132 None
133 }
134 })
135 .unwrap_or_default();
136 let pubsub = config
137 .value("cluster.coordinator")
138 .map(|id| id.to_string())
139 .and_then(|id| {
140 if let Some(store) = stores.pubsub_stores.get(&id) {
141 store.clone().into()
142 } else {
143 config.new_parse_error(
144 "cluster.coordinator",
145 format!("Coordinator backend {id:?} not found"),
146 );
147 None
148 }
149 })
150 .unwrap_or_default();
151 let mut directories =
152 Directories::parse(config, &stores, data.clone(), is_enterprise).await;
153 let directory = config
154 .value_require("storage.directory")
155 .map(|id| id.to_string())
156 .and_then(|id| {
157 if let Some(directory) = directories.directories.get(&id) {
158 directory.clone().into()
159 } else {
160 config.new_parse_error(
161 "storage.directory",
162 format!("Directory {id:?} not found"),
163 );
164 None
165 }
166 })
167 .unwrap_or_else(|| Arc::new(Directory::default()));
168 directories
169 .directories
170 .insert("*".to_string(), directory.clone());
171
172 if matches!(data, Store::None)
174 || matches!(&blob.backend, BlobBackend::Store(Store::None))
175 || matches!(lookup, InMemoryStore::Store(Store::None))
176 || matches!(fts, SearchStore::Store(Store::None))
177 {
178 data = Store::default();
179 blob = BlobStore::default();
180 lookup = InMemoryStore::default();
181 fts = SearchStore::default();
182 config.new_build_error(
183 "storage.*",
184 "One or more stores are missing, disabling all stores",
185 )
186 }
187
188 let groupware = GroupwareConfig::parse(config);
189 Self {
190 #[cfg(feature = "enterprise")]
194 enterprise,
195 sieve: Scripting::parse(config, &stores).await,
197 network: Network::parse(config),
198 smtp: SmtpConfig::parse(config).await,
199 jmap: JmapConfig::parse(config, &groupware),
200 imap: ImapConfig::parse(config),
201 oauth: OAuthConfig::parse(config),
202 acme: AcmeProviders::parse(config),
203 metrics: Metrics::parse(config),
204 spam: SpamFilterConfig::parse(config).await,
205 groupware,
206 storage: Storage {
207 data,
208 blob,
209 fts,
210 lookup,
211 pubsub,
212 directory,
213 directories: directories.directories,
214 purge_schedules: stores.purge_schedules,
215 config: config_manager,
216 stores: stores.stores,
217 lookups: stores.in_memory_stores,
218 blobs: stores.blob_stores,
219 ftss: stores.search_stores,
220 },
221 }
222 }
223
224 pub fn into_shared(self) -> ArcSwap<Self> {
225 ArcSwap::from_pointee(self)
226 }
227}
228
229pub fn build_rsa_keypair(pem: &str) -> Result<RsaKeyPair, String> {
230 match rustls_pemfile::read_one(&mut pem.as_bytes()) {
231 Ok(Some(rustls_pemfile::Item::Pkcs1Key(key))) => {
232 RsaKeyPair::from_der(key.secret_pkcs1_der())
233 .map_err(|err| format!("Failed to parse PKCS1 RSA key: {err}"))
234 }
235 Ok(Some(rustls_pemfile::Item::Pkcs8Key(key))) => {
236 RsaKeyPair::from_pkcs8(key.secret_pkcs8_der())
237 .map_err(|err| format!("Failed to parse PKCS8 RSA key: {err}"))
238 }
239 Err(err) => Err(format!("Failed to read PEM: {err}")),
240 Ok(Some(key)) => Err(format!("Unsupported key type: {key:?}")),
241 Ok(None) => Err("No RSA key found in PEM".to_string()),
242 }
243}
244
245pub fn build_ecdsa_pem(
246 alg: &'static ring::signature::EcdsaSigningAlgorithm,
247 pem: &str,
248) -> Result<EcdsaKeyPair, String> {
249 match rustls_pemfile::read_one(&mut pem.as_bytes()) {
250 Ok(Some(rustls_pemfile::Item::Pkcs8Key(key))) => EcdsaKeyPair::from_pkcs8(
251 alg,
252 key.secret_pkcs8_der(),
253 &ring::rand::SystemRandom::new(),
254 )
255 .map_err(|err| format!("Failed to parse PKCS8 ECDSA key: {err}")),
256 Err(err) => Err(format!("Failed to read PEM: {err}")),
257 Ok(Some(key)) => Err(format!("Unsupported key type: {key:?}")),
258 Ok(None) => Err("No ECDSA key found in PEM".to_string()),
259 }
260}