The join: the policy decides, features owns the listener objects, ListenerControl owns the running sockets, and only Server has both. Server::set_protocol_policy is what a click performs. It applies the locks to what was asked before storing anything (LP-21), so what is recorded is what the server allows. Closing removes each listener object and then stops its socket; opening puts the object back and then spawns it. The order is the point in both directions -- a socket stopped while its object remains returns on the next restart, and a socket spawned before its object exists has nothing to come back to. saved_listeners is carried over from the stored policy rather than taken from the request. A client never sets it, and a /set that omitted it would otherwise lose the listeners still waiting to come back. Putting a listener back has to bind a fresh socket, so it re-parses from the registry -- the objects are already back by then -- rather than trying to revive the saved one. Only main knows which session manager a protocol wants, so it leaves a spawner behind at startup and spawn_listener is now shared between that and the initial spawn. Without a spawner a restored listener is reported as pending a restart rather than promised, which is what the test servers will see. A listener that cannot be put back does not stop the others and stays saved for another try (LP-5). Still nothing an operator can reach: no JMAP method calls this yet, and no sign-in is refused. What it does do is close and reopen a port on a running server, which is the part that did not exist this morning.
167 lines
5.2 KiB
Rust
167 lines
5.2 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
#![warn(clippy::large_futures)]
|
|
#![warn(clippy::cast_possible_truncation)]
|
|
#![warn(clippy::cast_possible_wrap)]
|
|
#![warn(clippy::cast_sign_loss)]
|
|
|
|
use common::{
|
|
BuildServer, Inner,
|
|
config::server::{Listener, ServerProtocol},
|
|
manager::boot::BootManager,
|
|
network::TcpAcceptor,
|
|
};
|
|
use http::HttpSessionManager;
|
|
use imap::core::ImapSessionManager;
|
|
use managesieve::core::ManageSieveSessionManager;
|
|
use pop3::Pop3SessionManager;
|
|
use services::{StartServices, broadcast::subscriber::spawn_broadcast_subscriber};
|
|
use smtp::{StartQueueManager, core::SmtpSessionManager};
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use tokio::sync::watch;
|
|
use trc::Collector;
|
|
use utils::wait_for_shutdown;
|
|
|
|
#[cfg(feature = "dev_mode")]
|
|
pub mod test_data;
|
|
|
|
#[cfg(not(any(target_env = "msvc", target_os = "freebsd")))]
|
|
use tikv_jemallocator::Jemalloc;
|
|
|
|
#[cfg(not(any(target_env = "msvc", target_os = "freebsd")))]
|
|
#[global_allocator]
|
|
static GLOBAL: Jemalloc = Jemalloc;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> std::io::Result<()> {
|
|
// Install AWS-LC-RS as the default Rustls crypto provider
|
|
rustls::crypto::aws_lc_rs::default_provider()
|
|
.install_default()
|
|
.expect("failed to install aws-lc-rs as the default rustls crypto provider");
|
|
|
|
// Build the shared outbound TLS configurations
|
|
utils::http::init_shared_tls_configs();
|
|
|
|
// Load config and apply macros
|
|
let mut init = Box::pin(BootManager::init()).await;
|
|
|
|
// Migrate database
|
|
if let Err(err) = migration::try_migrate(&init.inner.build_server()).await {
|
|
trc::event!(
|
|
Server(trc::ServerEvent::StartupError),
|
|
Details = "Failed to migrate database, aborting startup.",
|
|
Reason = err,
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
// Init services
|
|
init.start_services().await;
|
|
init.start_queue_manager();
|
|
|
|
// Log configuration errors
|
|
init.bootstrap.log_errors();
|
|
init.bootstrap.log_warnings();
|
|
|
|
|
|
#[cfg(feature = "dev_mode")]
|
|
if std::env::var("INSERT_TEST_DATA").is_ok() {
|
|
let server = init.inner.build_server();
|
|
test_data::insert_test_data(&server).await;
|
|
server.insert_test_metrics().await;
|
|
}
|
|
|
|
// Spawn servers
|
|
// Each listener gets its own shutdown channel, registered under its id, so
|
|
// the legacy-protocols switch can close one protocol's ports and leave the
|
|
// rest accepting (legacy-protocols LP-2). The registry lives in `Data` and
|
|
// so outlives the listeners, which it must: it owns the sending ends.
|
|
let listener_control = &init.inner.data.listener_control;
|
|
let spawn_inner = init.inner.clone();
|
|
let (shutdown_tx, shutdown_rx) =
|
|
init.servers
|
|
.spawn_with_control(listener_control, |server, acceptor, shutdown_rx| {
|
|
spawn_listener(&spawn_inner, server, acceptor, shutdown_rx);
|
|
});
|
|
|
|
// Leave behind how to spawn a listener, so putting one back opens its port
|
|
// without a restart (LP-5). Only this file knows the session manager for a
|
|
// protocol, so only this file can say.
|
|
let spawn_inner = init.inner.clone();
|
|
init.inner
|
|
.data
|
|
.listener_control
|
|
.set_spawner(Box::new(move |server, acceptor, shutdown_rx| {
|
|
spawn_listener(&spawn_inner, server, acceptor, shutdown_rx);
|
|
}));
|
|
|
|
// Start broadcast subscriber
|
|
let inner = init.inner.clone();
|
|
spawn_broadcast_subscriber(init.inner, shutdown_rx);
|
|
|
|
// Wait for shutdown signal
|
|
wait_for_shutdown().await;
|
|
|
|
// Shutdown collector
|
|
Collector::shutdown();
|
|
|
|
// Stop services, then the listeners: the shutdown sender no longer reaches
|
|
// them, since each holds its own channel (LP-2).
|
|
let _ = shutdown_tx.send(true);
|
|
inner.data.listener_control.stop_all();
|
|
|
|
// Wait for services to finish
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Starts one listener under the session manager its protocol calls for.
|
|
///
|
|
/// Used twice: once for every listener at startup, and again whenever the
|
|
/// legacy-protocols switch puts a listener back (LP-5).
|
|
fn spawn_listener(
|
|
inner: &Arc<Inner>,
|
|
server: Listener,
|
|
acceptor: TcpAcceptor,
|
|
shutdown_rx: watch::Receiver<bool>,
|
|
) {
|
|
match &server.protocol {
|
|
ServerProtocol::Smtp | ServerProtocol::Lmtp => server.spawn(
|
|
SmtpSessionManager::new(inner.clone()),
|
|
inner.clone(),
|
|
acceptor,
|
|
shutdown_rx,
|
|
),
|
|
ServerProtocol::Http => server.spawn(
|
|
HttpSessionManager::new(inner.clone()),
|
|
inner.clone(),
|
|
acceptor,
|
|
shutdown_rx,
|
|
),
|
|
ServerProtocol::Imap => server.spawn(
|
|
ImapSessionManager::new(inner.clone()),
|
|
inner.clone(),
|
|
acceptor,
|
|
shutdown_rx,
|
|
),
|
|
ServerProtocol::Pop3 => server.spawn(
|
|
Pop3SessionManager::new(inner.clone()),
|
|
inner.clone(),
|
|
acceptor,
|
|
shutdown_rx,
|
|
),
|
|
ServerProtocol::ManageSieve => server.spawn(
|
|
ManageSieveSessionManager::new(inner.clone()),
|
|
inner.clone(),
|
|
acceptor,
|
|
shutdown_rx,
|
|
),
|
|
}
|
|
}
|