diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ffe88ba..2cd4a3c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. This projec ## [0.5.0] - 2023-12-27 +This version requires a database migration and introduces breaking changes in the configuration file. Please read the [UPGRADING.md](UPGRADING.md) file for more information. + ## Added - Performance enhancements: - Messages are parsed only once and their offsets stored in the database, which avoids having to parse them on every `FETCH` request. @@ -20,6 +22,7 @@ All notable changes to this project will be documented in this file. This projec - IMAP4rev1 `Recent` flag support, which improves compatibility with old IMAP clients. - LDAP bind authentication, to support some LDAP servers such as `lldap` which do not expose the userPassword attribute. - Messages marked a spam by the spam filter can now be automatically moved to the account's `Junk Mail` folder. +- Automatic creation of JMAP identities. ### Changed diff --git a/crates/directory/src/backend/internal/manage.rs b/crates/directory/src/backend/internal/manage.rs index 05510a3b..1bb1d9f0 100644 --- a/crates/directory/src/backend/internal/manage.rs +++ b/crates/directory/src/backend/internal/manage.rs @@ -22,11 +22,14 @@ */ use jmap_proto::types::collection::Collection; +use pwhash::sha512_crypt; use store::{ + rand::{distributions::Alphanumeric, thread_rng, Rng}, write::{ - assert::HashedValue, key::DeserializeBigEndian, BatchBuilder, DirectoryClass, ValueClass, + assert::HashedValue, key::DeserializeBigEndian, BatchBuilder, BitmapClass, DirectoryClass, + ValueClass, }, - Deserialize, IterateParams, Serialize, Store, ValueKey, U32_LEN, + BitmapKey, Deserialize, IterateParams, Serialize, Store, ValueKey, U32_LEN, }; use crate::{DirectoryError, ManagementError, Principal, QueryBy, Type}; @@ -210,6 +213,9 @@ impl ManageDirectory for Store { let mut batch = BatchBuilder::new(); let ptype = PrincipalIdType::new(principal.id, principal.typ.into_base_type()).serialize(); batch + .with_account_id(u32::MAX) + .with_collection(Collection::Principal) + .create_document(principal.id) .assert_value( ValueClass::Directory(DirectoryClass::NameToId( principal.name.clone().into_bytes(), @@ -902,6 +908,7 @@ impl ManageDirectory for Store { } async fn init(self) -> crate::Result { + // Create admin account if requested if let (Ok(admin_user), Ok(admin_pass)) = ( std::env::var("SET_ADMIN_USER"), std::env::var("SET_ADMIN_PASS"), @@ -933,6 +940,43 @@ impl ManageDirectory for Store { } std::process::exit(0); } + + // Create a default administrator account if none exists + if self + .get_bitmap(BitmapKey { + account_id: u32::MAX, + collection: Collection::Principal.into(), + class: BitmapClass::DocumentIds, + block_num: 0, + }) + .await? + .unwrap_or_default() + .is_empty() + { + let secret = thread_rng() + .sample_iter(Alphanumeric) + .take(12) + .map(char::from) + .collect::(); + let hashed_secret = sha512_crypt::hash(&secret).unwrap(); + + self.create_account(Principal { + typ: Type::Superuser, + quota: 0, + name: "admin".to_string(), + secrets: vec![hashed_secret], + emails: vec![], + member_of: vec![], + description: "Superuser".to_string().into(), + ..Default::default() + }) + .await?; + + tracing::info!( + "Created default administrator account \"admin\" with password {secret:?}." + ) + } + Ok(self) } } diff --git a/crates/install/src/main.rs b/crates/install/src/main.rs index ba81ffd5..fc132cd7 100644 --- a/crates/install/src/main.rs +++ b/crates/install/src/main.rs @@ -33,7 +33,6 @@ use base64::{engine::general_purpose, Engine}; use clap::{Parser, ValueEnum}; use dialoguer::{console::Term, theme::ColorfulTheme, Input, Select}; use openssl::rsa::Rsa; -use pwhash::sha512_crypt; use rand::{distributions::Alphanumeric, thread_rng, Rng}; const CONFIG_URL: &str = "https://get.stalw.art/resources/config.zip"; @@ -635,19 +634,9 @@ fn main() -> std::io::Result<()> { } if is_internal { - let secret = thread_rng() - .sample_iter(Alphanumeric) - .take(12) - .map(char::from) - .collect::(); - let hashed_secret = sha512_crypt::hash(&secret).unwrap(); eprintln!( - "\nšŸ”‘ To create the administrator account 'admin' with password '{secret}', execute:\nšŸ”‘ ", + "\nšŸ”‘ The administrator account is 'admin' and the password can be found in the log files at {}/logs.", base_path.display() ); - eprintln!( - "šŸ”‘ $ SET_ADMIN_USER=\"admin\" SET_ADMIN_PASS=\"{hashed_secret}\" {}/bin/stalwart-mail --config={}/etc/config.toml", - base_path.display(), base_path.display() - ) } eprintln!("\nšŸŽ‰ Installation completed!\n\nāœ… {dkim_instructions}\n"); diff --git a/crates/main/src/main.rs b/crates/main/src/main.rs index 6ab47dff..4324c0eb 100644 --- a/crates/main/src/main.rs +++ b/crates/main/src/main.rs @@ -45,6 +45,17 @@ static GLOBAL: Jemalloc = Jemalloc; #[tokio::main] async fn main() -> std::io::Result<()> { let config = Config::init(); + + // Enable tracing + let _tracer = enable_tracing( + &config, + &format!( + "Starting Stalwart Mail Server v{}...", + env!("CARGO_PKG_VERSION"), + ), + ) + .failed("Failed to enable tracing"); + let servers = config.parse_servers().failed("Invalid configuration"); let stores = config.parse_stores().await.failed("Invalid configuration"); let directory = config @@ -63,16 +74,6 @@ async fn main() -> std::io::Result<()> { // Bind ports and drop privileges servers.bind(&config); - // Enable tracing - let _tracer = enable_tracing( - &config, - &format!( - "Starting Stalwart Mail Server v{}...", - env!("CARGO_PKG_VERSION"), - ), - ) - .failed("Failed to enable tracing"); - // Init servers let (delivery_tx, delivery_rx) = mpsc::channel(IPC_CHANNEL_BUFFER); let smtp = SMTP::init(&config, &servers, &stores, &directory, delivery_tx) diff --git a/crates/managesieve/src/core/client.rs b/crates/managesieve/src/core/client.rs index 04471d6a..90745eaf 100644 --- a/crates/managesieve/src/core/client.rs +++ b/crates/managesieve/src/core/client.rs @@ -146,7 +146,7 @@ impl Session { Ok(len) } Err(err) => { - tracing::debug!( + tracing::trace!( parent: &self.span, event = "error", "Failed to read from stream: {:?}", err diff --git a/crates/smtp/src/inbound/session.rs b/crates/smtp/src/inbound/session.rs index f21eee9e..6c690007 100644 --- a/crates/smtp/src/inbound/session.rs +++ b/crates/smtp/src/inbound/session.rs @@ -369,7 +369,7 @@ impl Session { Err(err) => err, }; - tracing::debug!(parent: &self.span, + tracing::trace!(parent: &self.span, event = "error", "Failed to write to stream: {:?}", err); Err(()) @@ -389,7 +389,7 @@ impl Session { Ok(len) } Err(err) => { - tracing::debug!( + tracing::trace!( parent: &self.span, event = "error", "Failed to read from stream: {:?}", err diff --git a/crates/utils/src/listener/listen.rs b/crates/utils/src/listener/listen.rs index a615b62e..53f86b51 100644 --- a/crates/utils/src/listener/listen.rs +++ b/crates/utils/src/listener/listen.rs @@ -164,7 +164,7 @@ impl Server { }; } Err(err) => { - tracing::debug!(context = "io", + tracing::trace!(context = "io", event = "error", instance = instance.id, protocol = ?instance.protocol,