v0.10.6
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -2,6 +2,20 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.10.6] - 2024-11-07
|
||||
|
||||
To upgrade replace the `stalwart-mail` binary and then upgrade to the latest web-admin.
|
||||
|
||||
### Added
|
||||
- Enterprise license automatic renewals before expiration (disabled by default).
|
||||
- Allow to LDAP search using bind dn instead of auth bind connection when bind auth is enabled (#873)
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
- Include `preferred_username` and `email` in OIDC `id_token`.
|
||||
- Verify roles and permissions when creating or modifying accounts (#874)
|
||||
|
||||
## [0.10.5] - 2024-10-15
|
||||
|
||||
To upgrade replace the `stalwart-mail` binary.
|
||||
|
||||
28
Cargo.lock
generated
28
Cargo.lock
generated
@@ -1051,7 +1051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "common"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"aes-gcm-siv",
|
||||
"ahash 0.8.11",
|
||||
@@ -1667,7 +1667,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "directory"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"argon2",
|
||||
@@ -3010,7 +3010,7 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
||||
|
||||
[[package]]
|
||||
name = "imap"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"common",
|
||||
@@ -3222,7 +3222,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jmap"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
@@ -3303,7 +3303,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jmap_proto"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"fast-float",
|
||||
@@ -3654,7 +3654,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mail-server"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"common",
|
||||
"directory",
|
||||
@@ -3673,7 +3673,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "managesieve"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bincode",
|
||||
@@ -3951,7 +3951,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nlp"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bincode",
|
||||
@@ -4502,7 +4502,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pop3"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"common",
|
||||
"directory",
|
||||
@@ -6059,7 +6059,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "smtp"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bincode",
|
||||
@@ -6175,7 +6175,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "stalwart-cli"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"console",
|
||||
@@ -6206,7 +6206,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "store"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"arc-swap",
|
||||
@@ -6850,7 +6850,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "trc"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"base64 0.22.1",
|
||||
@@ -7093,7 +7093,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "utils"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"base64 0.22.1",
|
||||
|
||||
@@ -5,7 +5,7 @@ authors = ["Stalwart Labs Ltd. <hello@stalw.art>"]
|
||||
license = "AGPL-3.0-only OR LicenseRef-SEL"
|
||||
repository = "https://github.com/stalwartlabs/cli"
|
||||
homepage = "https://github.com/stalwartlabs/cli"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
resolver = "2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "common"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -74,7 +74,8 @@ impl Core {
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
#[cfg(feature = "enterprise")]
|
||||
let enterprise = crate::enterprise::Enterprise::parse(config, &stores, &data).await;
|
||||
let enterprise =
|
||||
crate::enterprise::Enterprise::parse(config, &config_manager, &stores, &data).await;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
let is_enterprise = enterprise.is_some();
|
||||
|
||||
@@ -17,30 +17,93 @@ use trc::{EventType, MetricType, TOTAL_EVENT_COUNT};
|
||||
use utils::config::{
|
||||
cron::SimpleCron,
|
||||
utils::{AsKey, ParseValue},
|
||||
Config,
|
||||
Config, ConfigKey,
|
||||
};
|
||||
|
||||
use crate::expr::{tokenizer::TokenMap, Expression};
|
||||
use crate::{
|
||||
expr::{tokenizer::TokenMap, Expression},
|
||||
manager::config::ConfigManager,
|
||||
};
|
||||
|
||||
use super::{
|
||||
license::LicenseValidator, llm::AiApiConfig, AlertContent, AlertContentToken, AlertMethod,
|
||||
license::LicenseKey, llm::AiApiConfig, AlertContent, AlertContentToken, AlertMethod,
|
||||
Enterprise, MetricAlert, MetricStore, TraceStore, Undelete,
|
||||
};
|
||||
|
||||
impl Enterprise {
|
||||
pub async fn parse(config: &mut Config, stores: &Stores, data: &Store) -> Option<Self> {
|
||||
let license = match LicenseValidator::new()
|
||||
.try_parse(config.value("enterprise.license-key")?)
|
||||
.and_then(|key| {
|
||||
key.into_validated_key(config.value("lookup.default.hostname").unwrap_or_default())
|
||||
}) {
|
||||
Ok(key) => key,
|
||||
pub async fn parse(
|
||||
config: &mut Config,
|
||||
config_manager: &ConfigManager,
|
||||
stores: &Stores,
|
||||
data: &Store,
|
||||
) -> Option<Self> {
|
||||
let server_hostname = config.value("lookup.default.hostname")?;
|
||||
let mut update_license = None;
|
||||
|
||||
let license_result = match (
|
||||
config.value("enterprise.license-key"),
|
||||
config.value("enterprise.api-key"),
|
||||
) {
|
||||
(Some(license_key), maybe_api_key) => {
|
||||
match (LicenseKey::new(license_key, server_hostname), maybe_api_key) {
|
||||
(Ok(license), Some(api_key)) if license.is_near_expiration() => Ok(license
|
||||
.try_renew(api_key)
|
||||
.await
|
||||
.map(|result| {
|
||||
update_license = Some(result.encoded_key);
|
||||
result.key
|
||||
})
|
||||
.unwrap_or(license)),
|
||||
(Ok(license), None) => Ok(license),
|
||||
(Err(_), Some(api_key)) => LicenseKey::invalid(server_hostname)
|
||||
.try_renew(api_key)
|
||||
.await
|
||||
.map(|result| {
|
||||
update_license = Some(result.encoded_key);
|
||||
result.key
|
||||
}),
|
||||
(maybe_license, _) => maybe_license,
|
||||
}
|
||||
}
|
||||
(None, Some(api_key)) => LicenseKey::invalid(server_hostname)
|
||||
.try_renew(api_key)
|
||||
.await
|
||||
.map(|result| {
|
||||
update_license = Some(result.encoded_key);
|
||||
result.key
|
||||
}),
|
||||
(None, None) => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Report error
|
||||
let license = match license_result {
|
||||
Ok(license) => license,
|
||||
Err(err) => {
|
||||
config.new_build_warning("enterprise.license-key", err.to_string());
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Update the license if a new one was obtained
|
||||
if let Some(license) = update_license {
|
||||
config
|
||||
.keys
|
||||
.insert("enterprise.license-key".to_string(), license.clone());
|
||||
if let Err(err) = config_manager
|
||||
.set([ConfigKey {
|
||||
key: "enterprise.license-key".to_string(),
|
||||
value: license.to_string(),
|
||||
}])
|
||||
.await
|
||||
{
|
||||
trc::error!(err
|
||||
.caused_by(trc::location!())
|
||||
.details("Failed to update license key"));
|
||||
}
|
||||
}
|
||||
|
||||
match data
|
||||
.count_principals(None, Type::Individual.into(), None)
|
||||
.await
|
||||
|
||||
@@ -21,21 +21,26 @@
|
||||
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
time::{Duration, SystemTime},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use ring::signature::{Ed25519KeyPair, UnparsedPublicKey, ED25519};
|
||||
use hyper::{header::AUTHORIZATION, HeaderMap};
|
||||
use ring::signature::{UnparsedPublicKey, ED25519};
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
use store::write::now;
|
||||
use trc::ServerEvent;
|
||||
|
||||
use crate::manager::fetch_resource;
|
||||
|
||||
//const LICENSING_API: &str = "https://localhost:444/api/license/";
|
||||
const LICENSING_API: &str = "https://license.stalw.art/api/license/";
|
||||
const RENEW_THRESHOLD: u64 = 60 * 60 * 24 * 4; // 4 days
|
||||
|
||||
pub struct LicenseValidator {
|
||||
public_key: UnparsedPublicKey<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub struct LicenseGenerator {
|
||||
key_pair: Ed25519KeyPair,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LicenseKey {
|
||||
pub valid_to: u64,
|
||||
@@ -47,11 +52,18 @@ pub struct LicenseKey {
|
||||
#[derive(Debug)]
|
||||
pub enum LicenseError {
|
||||
Expired,
|
||||
InvalidDomain { domain: String },
|
||||
DomainMismatch { issued_to: String, current: String },
|
||||
Parse,
|
||||
Validation,
|
||||
Decode,
|
||||
InvalidParameters,
|
||||
RenewalFailed { reason: String },
|
||||
}
|
||||
|
||||
pub struct RenewedLicense {
|
||||
pub key: LicenseKey,
|
||||
pub encoded_key: String,
|
||||
}
|
||||
|
||||
const U64_LEN: usize = std::mem::size_of::<u64>();
|
||||
@@ -142,68 +154,102 @@ impl LicenseValidator {
|
||||
}
|
||||
|
||||
impl LicenseKey {
|
||||
pub fn new(domain: String, accounts: u32, expires_in: u64) -> Self {
|
||||
let now = SystemTime::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
LicenseKey {
|
||||
valid_from: now - 300,
|
||||
valid_to: now + expires_in + 300,
|
||||
domain,
|
||||
accounts,
|
||||
pub fn new(
|
||||
license_key: impl AsRef<str>,
|
||||
hostname: impl AsRef<str>,
|
||||
) -> Result<Self, LicenseError> {
|
||||
LicenseValidator::new()
|
||||
.try_parse(license_key)
|
||||
.and_then(|key| {
|
||||
let local_domain = Self::base_domain(hostname)?;
|
||||
let license_domain = Self::base_domain(&key.domain)?;
|
||||
if local_domain == license_domain {
|
||||
Ok(key)
|
||||
} else {
|
||||
Err(LicenseError::DomainMismatch {
|
||||
issued_to: license_domain,
|
||||
current: local_domain,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn invalid(domain: impl AsRef<str>) -> Self {
|
||||
LicenseKey {
|
||||
valid_from: 0,
|
||||
valid_to: 0,
|
||||
domain: Self::base_domain(domain).unwrap_or_default(),
|
||||
accounts: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_renew(&self, api_key: &str) -> Result<RenewedLicense, LicenseError> {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
AUTHORIZATION,
|
||||
format!("Bearer {api_key}")
|
||||
.parse()
|
||||
.map_err(|_| LicenseError::Validation)?,
|
||||
);
|
||||
|
||||
trc::event!(
|
||||
Server(ServerEvent::Licensing),
|
||||
Details = "Attempting to renew Enterprise license from license.stalw.art",
|
||||
);
|
||||
|
||||
match fetch_resource(&format!("{}{}", LICENSING_API, self.domain), headers.into())
|
||||
.await
|
||||
.and_then(|bytes| {
|
||||
String::from_utf8(bytes)
|
||||
.map_err(|_| String::from("Failed to UTF-8 decode server response"))
|
||||
}) {
|
||||
Ok(encoded_key) => match LicenseKey::new(&encoded_key, &self.domain) {
|
||||
Ok(key) => Ok(RenewedLicense { key, encoded_key }),
|
||||
Err(err) => {
|
||||
trc::event!(
|
||||
Server(ServerEvent::Licensing),
|
||||
Details = "Failed to decode license renewal",
|
||||
Reason = err.to_string(),
|
||||
);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
trc::event!(
|
||||
Server(ServerEvent::Licensing),
|
||||
Details = "Failed to renew Enterprise license",
|
||||
Reason = err.clone(),
|
||||
);
|
||||
Err(LicenseError::RenewalFailed { reason: err })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_near_expiration(&self) -> bool {
|
||||
let now = now();
|
||||
self.valid_to.saturating_sub(now) <= RENEW_THRESHOLD
|
||||
}
|
||||
|
||||
pub fn expires_in(&self) -> Duration {
|
||||
Duration::from_secs(
|
||||
self.valid_to.saturating_sub(
|
||||
SystemTime::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.unwrap_or_default()
|
||||
.as_secs(),
|
||||
),
|
||||
)
|
||||
Duration::from_secs(self.valid_to.saturating_sub(now()))
|
||||
}
|
||||
|
||||
pub fn renew_in(&self) -> Duration {
|
||||
Duration::from_secs(self.valid_to.saturating_sub(now() + RENEW_THRESHOLD))
|
||||
}
|
||||
|
||||
pub fn is_expired(&self) -> bool {
|
||||
let now = SystemTime::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let now = now();
|
||||
now >= self.valid_to || now < self.valid_from
|
||||
}
|
||||
|
||||
pub fn into_validated_key(self, hostname: impl AsRef<str>) -> Result<Self, LicenseError> {
|
||||
let local_domain = psl::domain_str(hostname.as_ref()).unwrap_or("invalid-hostname");
|
||||
let license_domain = psl::domain_str(&self.domain).expect("Invalid license domain");
|
||||
if local_domain != license_domain {
|
||||
Err(LicenseError::DomainMismatch {
|
||||
issued_to: license_domain.to_string(),
|
||||
current: local_domain.to_string(),
|
||||
pub fn base_domain(domain: impl AsRef<str>) -> Result<String, LicenseError> {
|
||||
let domain = domain.as_ref();
|
||||
psl::domain_str(domain)
|
||||
.map(|d| d.to_string())
|
||||
.ok_or(LicenseError::InvalidDomain {
|
||||
domain: domain.to_string(),
|
||||
})
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LicenseGenerator {
|
||||
pub fn new(pkcs8_der: impl AsRef<[u8]>) -> Self {
|
||||
Self {
|
||||
key_pair: Ed25519KeyPair::from_pkcs8(pkcs8_der.as_ref()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate(&self, key: LicenseKey) -> String {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(&key.valid_from.to_le_bytes());
|
||||
bytes.extend_from_slice(&key.valid_to.to_le_bytes());
|
||||
bytes.extend_from_slice(&key.accounts.to_le_bytes());
|
||||
bytes.extend_from_slice(&(key.domain.len() as u32).to_le_bytes());
|
||||
bytes.extend_from_slice(key.domain.as_bytes());
|
||||
bytes.extend_from_slice(self.key_pair.sign(&bytes).as_ref());
|
||||
STANDARD.encode(&bytes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +267,12 @@ impl Display for LicenseError {
|
||||
"License issued to domain {issued_to:?} does not match {current:?}",
|
||||
)
|
||||
}
|
||||
LicenseError::InvalidDomain { domain } => {
|
||||
write!(f, "Invalid domain {domain:?}")
|
||||
}
|
||||
LicenseError::RenewalFailed { reason } => {
|
||||
write!(f, "Failed to renew license: {reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use hyper::HeaderMap;
|
||||
|
||||
use crate::USER_AGENT;
|
||||
|
||||
use self::config::ConfigManager;
|
||||
@@ -32,35 +34,57 @@ impl ConfigManager {
|
||||
format!("Failed to fetch configuration key 'resource.{resource_id}': {err}",)
|
||||
})?
|
||||
{
|
||||
fetch_resource(&url).await
|
||||
fetch_resource(&url, None).await
|
||||
} else {
|
||||
match resource_id {
|
||||
"spam-filter" => fetch_resource(DEFAULT_SPAMFILTER_URL).await,
|
||||
"webadmin" => fetch_resource(DEFAULT_WEBADMIN_URL).await,
|
||||
"spam-filter" => fetch_resource(DEFAULT_SPAMFILTER_URL, None).await,
|
||||
"webadmin" => fetch_resource(DEFAULT_WEBADMIN_URL, None).await,
|
||||
_ => Err(format!("Unknown resource: {resource_id}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_resource(url: &str) -> Result<Vec<u8>, String> {
|
||||
pub async fn fetch_resource(url: &str, headers: Option<HeaderMap>) -> Result<Vec<u8>, String> {
|
||||
if let Some(path) = url.strip_prefix("file://") {
|
||||
tokio::fs::read(path)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to read {path}: {err}"))
|
||||
} else {
|
||||
reqwest::Client::builder()
|
||||
let response = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(60))
|
||||
.danger_accept_invalid_certs(is_localhost_url(url))
|
||||
.user_agent(USER_AGENT)
|
||||
.build()
|
||||
.unwrap_or_default()
|
||||
.get(url)
|
||||
.headers(headers.unwrap_or_default())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| format!("Failed to fetch {url}: {err}"))?
|
||||
.map_err(|err| format!("Failed to fetch {url}: {err}"))?;
|
||||
|
||||
if response.status().is_success() {
|
||||
response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|err| format!("Failed to fetch {url}: {err}"))
|
||||
.map(|bytes| bytes.to_vec())
|
||||
} else {
|
||||
let code = response.status().canonical_reason().unwrap_or_default();
|
||||
let reason = response.text().await.unwrap_or_default();
|
||||
|
||||
Err(format!(
|
||||
"Failed to fetch {url}: Code: {code}, Details: {reason}",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_localhost_url(url: &str) -> bool {
|
||||
url.split_once("://")
|
||||
.map(|(_, url)| url.split_once('/').map_or(url, |(host, _)| host))
|
||||
.map_or(false, |host| {
|
||||
let host = host.rsplit_once(':').map_or(host, |(host, _)| host);
|
||||
host == "localhost" || host == "127.0.0.1" || host == "[::1]"
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "directory"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "imap"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "jmap_proto"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "jmap"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ enum ActionClass {
|
||||
#[cfg(feature = "enterprise")]
|
||||
AlertMetrics,
|
||||
#[cfg(feature = "enterprise")]
|
||||
ValidateLicense,
|
||||
RenewLicense,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -123,8 +123,8 @@ pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEv
|
||||
#[cfg(feature = "enterprise")]
|
||||
if let Some(enterprise) = &server.core.enterprise {
|
||||
queue.schedule(
|
||||
Instant::now() + enterprise.license.expires_in(),
|
||||
ActionClass::ValidateLicense,
|
||||
Instant::now() + enterprise.license.renew_in(),
|
||||
ActionClass::RenewLicense,
|
||||
);
|
||||
|
||||
if let Some(metrics_store) = enterprise.metrics_store.as_ref() {
|
||||
@@ -173,10 +173,10 @@ pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEv
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
#[cfg(feature = "enterprise")]
|
||||
if let Some(enterprise) = &server.core.enterprise {
|
||||
if !queue.has_action(&ActionClass::ValidateLicense) {
|
||||
if !queue.has_action(&ActionClass::RenewLicense) {
|
||||
queue.schedule(
|
||||
Instant::now() + enterprise.license.expires_in(),
|
||||
ActionClass::ValidateLicense,
|
||||
Instant::now() + enterprise.license.renew_in(),
|
||||
ActionClass::RenewLicense,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -616,15 +616,29 @@ pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEv
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
ActionClass::ValidateLicense => {
|
||||
ActionClass::RenewLicense => {
|
||||
match server.reload().await {
|
||||
Ok(result) => {
|
||||
if let Some(new_core) = result.new_core {
|
||||
if let Some(enterprise) = &new_core.enterprise {
|
||||
let renew_in =
|
||||
if enterprise.license.is_near_expiration() {
|
||||
// Something went wrong during renewal, try again in 1 day or 1 hour,
|
||||
// depending on the time left on the license
|
||||
if enterprise.license.expires_in()
|
||||
< Duration::from_secs(86400)
|
||||
{
|
||||
Duration::from_secs(3600)
|
||||
} else {
|
||||
Duration::from_secs(86400)
|
||||
}
|
||||
} else {
|
||||
enterprise.license.renew_in()
|
||||
};
|
||||
|
||||
queue.schedule(
|
||||
Instant::now()
|
||||
+ enterprise.license.expires_in(),
|
||||
ActionClass::ValidateLicense,
|
||||
Instant::now() + renew_in,
|
||||
ActionClass::RenewLicense,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ homepage = "https://stalw.art"
|
||||
keywords = ["imap", "jmap", "smtp", "email", "mail", "server"]
|
||||
categories = ["email"]
|
||||
license = "AGPL-3.0-only OR LicenseRef-SEL"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "managesieve"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nlp"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "pop3"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ homepage = "https://stalw.art/smtp"
|
||||
keywords = ["smtp", "email", "mail", "server"]
|
||||
categories = ["email"]
|
||||
license = "AGPL-3.0-only OR LicenseRef-SEL"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "store"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "trc"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "utils"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user