Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
Session, State,
protocol::{Command, Mechanism, request},
};
use common::{
auth::AuthRequest,
network::{SessionStream, limiter::LimiterResult},
};
use directory::Credentials;
use mail_parser::decoders::base64::base64_decode;
use registry::schema::enums::Permission;
impl<T: SessionStream> Session<T> {
pub async fn handle_sasl(
&mut self,
mechanism: Mechanism,
mut params: Vec<String>,
) -> trc::Result<()> {
match mechanism {
Mechanism::Plain | Mechanism::OAuthBearer | Mechanism::XOauth2 => {
if !params.is_empty() {
let credentials = base64_decode(params.pop().unwrap().as_bytes())
.and_then(|challenge| {
if mechanism == Mechanism::Plain {
Credentials::decode_sasl_challenge_plain(&challenge)
} else {
Credentials::decode_sasl_challenge_oauth(&challenge)
}
})
.ok_or_else(|| {
trc::AuthEvent::Error
.into_err()
.details("Invalid SASL challenge")
})?;
Box::pin(self.handle_auth(credentials)).await
} else {
// TODO: This hack is temporary until the SASL library is developed
self.receiver.state = request::State::Argument {
request: Command::Auth {
mechanism: mechanism.as_str().as_bytes().to_vec(),
params: vec![],
},
num: 1,
last_is_space: true,
};
self.write_bytes("+\r\n").await
}
}
_ => Err(trc::AuthEvent::Error
.into_err()
.details("Authentication mechanism not supported.")),
}
}
pub async fn handle_auth(&mut self, credentials: Credentials) -> trc::Result<()> {
// Authenticate
let access_token = self
.server
.authenticate(&AuthRequest::from_credentials(
credentials,
self.session_id,
self.remote_addr,
))
.await
.map_err(|err| {
if err.matches(trc::EventType::Auth(trc::AuthEvent::Failed)) {
match &self.state {
State::NotAuthenticated {
auth_failures,
username,
} if *auth_failures < self.server.core.imap.max_auth_failures => {
self.state = State::NotAuthenticated {
auth_failures: auth_failures + 1,
username: username.clone(),
};
}
_ => {
return trc::AuthEvent::TooManyAttempts.into_err().caused_by(err);
}
}
}
err
})
.and_then(|token| token.assert_has_permission(Permission::Pop3Authenticate))?;
// Enforce concurrency limits
let in_flight = match access_token.is_imap_request_allowed() {
LimiterResult::Allowed(in_flight) => Some(in_flight),
LimiterResult::Forbidden => {
return Err(trc::LimitEvent::ConcurrentRequest.into_err());
}
LimiterResult::Disabled => None,
};
// Fetch mailbox
let mailbox = self.fetch_mailbox(access_token.account_id()).await?;
// Create session
self.state = State::Authenticated {
in_flight,
mailbox,
access_token,
};
self.write_ok("Authentication successful").await
}
}
+137
View File
@@ -0,0 +1,137 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::time::Instant;
use common::network::SessionStream;
use email::message::delete::EmailDeletion;
use registry::schema::enums::Permission;
use store::{roaring::RoaringBitmap, write::BatchBuilder};
use trc::AddContext;
use crate::{Session, State, protocol::response::Response};
impl<T: SessionStream> Session<T> {
pub async fn handle_dele(&mut self, msgs: Vec<u32>) -> trc::Result<()> {
// Validate access
self.state
.access_token()
.enforce_permission(Permission::Pop3Dele)?;
let op_start = Instant::now();
let mailbox = self.state.mailbox_mut();
let mut response = Vec::new();
for msg in &msgs {
if let Some(message) = mailbox.messages.get_mut(msg.saturating_sub(1) as usize) {
if !message.deleted {
response.extend_from_slice(format!("+OK message {msg} deleted\r\n").as_bytes());
message.deleted = true;
} else {
response.extend_from_slice(
format!("-ERR message {msg} already deleted\r\n").as_bytes(),
);
}
} else {
response.extend_from_slice("-ERR no such message\r\n".as_bytes());
}
}
trc::event!(
Pop3(trc::Pop3Event::Delete),
SpanId = self.session_id,
Total = msgs.len(),
Elapsed = op_start.elapsed()
);
self.write_bytes(response).await
}
pub async fn handle_rset(&mut self) -> trc::Result<()> {
let op_start = Instant::now();
let mut count = 0;
let mailbox = self.state.mailbox_mut();
for message in &mut mailbox.messages {
if message.deleted {
count += 1;
message.deleted = false;
}
}
trc::event!(
Pop3(trc::Pop3Event::Reset),
SpanId = self.session_id,
Total = count as u64,
Elapsed = op_start.elapsed()
);
self.write_ok(format!("{count} messages undeleted")).await
}
pub async fn handle_quit(&mut self) -> trc::Result<()> {
let op_start = Instant::now();
let mut deleted_docs = Vec::new();
if let State::Authenticated { mailbox, .. } = &self.state {
let mut deleted = RoaringBitmap::new();
for message in &mailbox.messages {
if message.deleted {
deleted.insert(message.id);
deleted_docs.push(trc::Value::from(message.id));
}
}
if !deleted.is_empty() {
let num_deleted = deleted.len();
let mut batch = BatchBuilder::new();
let not_deleted = self
.server
.emails_delete(
mailbox.account_id,
self.state.access_token().tenant_id(),
&mut batch,
deleted,
)
.await
.caused_by(trc::location!())?;
if !batch.is_empty() {
self.server
.commit_batch(batch)
.await
.caused_by(trc::location!())?;
self.server.notify_task_queue();
}
if not_deleted.is_empty() {
self.write_ok(format!(
"Stalwart POP3 bids you farewell ({num_deleted} messages deleted)."
))
.await?;
} else {
self.write_bytes(
Response::Err::<u32>("Some messages could not be deleted".into())
.serialize(),
)
.await?;
}
} else {
self.write_ok("Stalwart POP3 bids you farewell (no messages deleted).")
.await?;
}
} else {
self.write_ok("Stalwart POP3 bids you farewell.").await?;
}
trc::event!(
Pop3(trc::Pop3Event::Quit),
SpanId = self.session_id,
DocumentId = deleted_docs,
Elapsed = op_start.elapsed()
);
Ok(())
}
}
+91
View File
@@ -0,0 +1,91 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{Session, protocol::response::Response};
use common::network::SessionStream;
use email::message::metadata::MessageMetadata;
use registry::schema::enums::Permission;
use std::time::Instant;
use store::{
ValueKey,
write::{AlignedBytes, Archive},
};
use trc::AddContext;
use types::{collection::Collection, field::EmailField};
use utils::chained_bytes::ChainedBytes;
impl<T: SessionStream> Session<T> {
pub async fn handle_fetch(&mut self, msg: u32, lines: Option<u32>) -> trc::Result<()> {
// Validate access
self.state
.access_token()
.enforce_permission(Permission::Pop3Retr)?;
let op_start = Instant::now();
let mailbox = self.state.mailbox();
if let Some(message) = mailbox.messages.get(msg.saturating_sub(1) as usize) {
if let Some(metadata_) = self
.server
.store()
.get_value::<Archive<AlignedBytes>>(ValueKey::property(
mailbox.account_id,
Collection::Email,
message.id,
EmailField::Metadata,
))
.await
.caused_by(trc::location!())?
{
let metadata = metadata_
.unarchive::<MessageMetadata>()
.caused_by(trc::location!())?;
if let Some(bytes) = self
.server
.blob_store()
.get_blob(metadata.blob_hash.0.as_slice(), 0..usize::MAX)
.await
.caused_by(trc::location!())?
{
trc::event!(
Pop3(trc::Pop3Event::Fetch),
SpanId = self.session_id,
DocumentId = message.id,
Elapsed = op_start.elapsed()
);
let bytes = ChainedBytes::new(metadata.raw_headers.as_ref())
.with_last(
bytes
.get(metadata.blob_body_offset.to_native() as usize..)
.unwrap_or_default(),
)
.get_full_range();
self.write_bytes(
Response::Message::<u32> {
bytes,
lines: lines.unwrap_or(0),
}
.serialize(),
)
.await
} else {
Err(trc::Pop3Event::Error
.into_err()
.details("Failed to fetch message. Perhaps another session deleted it?")
.caused_by(trc::location!()))
}
} else {
Err(trc::Pop3Event::Error
.into_err()
.details("Failed to fetch message. Perhaps another session deleted it?")
.caused_by(trc::location!()))
}
} else {
Err(trc::Pop3Event::Error.into_err().details("No such message."))
}
}
}
+125
View File
@@ -0,0 +1,125 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::time::Instant;
use common::network::SessionStream;
use registry::schema::enums::Permission;
use crate::{Session, protocol::response::Response};
impl<T: SessionStream> Session<T> {
pub async fn handle_list(&mut self, msg: Option<u32>) -> trc::Result<()> {
// Validate access
self.state
.access_token()
.enforce_permission(Permission::Pop3List)?;
let op_start = Instant::now();
let mailbox = self.state.mailbox();
if let Some(msg) = msg {
if let Some(message) = mailbox.messages.get(msg.saturating_sub(1) as usize) {
trc::event!(
Pop3(trc::Pop3Event::ListMessage),
SpanId = self.session_id,
DocumentId = message.id,
Size = message.size,
Elapsed = op_start.elapsed()
);
self.write_ok(format!("{} {}", msg, message.size)).await
} else {
Err(trc::Pop3Event::Error
.into_err()
.details("No such message.")
.caused_by(trc::location!()))
}
} else {
trc::event!(
Pop3(trc::Pop3Event::List),
SpanId = self.session_id,
Total = mailbox.messages.len(),
Elapsed = op_start.elapsed()
);
self.write_bytes(
Response::List(mailbox.messages.iter().map(|m| m.size).collect::<Vec<_>>())
.serialize(),
)
.await
}
}
pub async fn handle_uidl(&mut self, msg: Option<u32>) -> trc::Result<()> {
// Validate access
self.state
.access_token()
.enforce_permission(Permission::Pop3Uidl)?;
let op_start = Instant::now();
let mailbox = self.state.mailbox();
if let Some(msg) = msg {
if let Some(message) = mailbox.messages.get(msg.saturating_sub(1) as usize) {
trc::event!(
Pop3(trc::Pop3Event::UidlMessage),
SpanId = self.session_id,
DocumentId = message.id,
Uid = message.uid,
UidValidity = mailbox.uid_validity,
Elapsed = op_start.elapsed()
);
self.write_ok(format!("{} {}{}", msg, mailbox.uid_validity, message.uid))
.await
} else {
Err(trc::Pop3Event::Error
.into_err()
.details("No such message.")
.caused_by(trc::location!()))
}
} else {
trc::event!(
Pop3(trc::Pop3Event::Uidl),
SpanId = self.session_id,
Total = mailbox.messages.len(),
Elapsed = op_start.elapsed()
);
self.write_bytes(
Response::List(
mailbox
.messages
.iter()
.map(|m| format!("{}{}", mailbox.uid_validity, m.uid))
.collect::<Vec<_>>(),
)
.serialize(),
)
.await
}
}
pub async fn handle_stat(&mut self) -> trc::Result<()> {
// Validate access
self.state
.access_token()
.enforce_permission(Permission::Pop3Stat)?;
let op_start = Instant::now();
let mailbox = self.state.mailbox();
trc::event!(
Pop3(trc::Pop3Event::Stat),
SpanId = self.session_id,
Total = mailbox.total,
Size = mailbox.size,
Elapsed = op_start.elapsed()
);
self.write_ok(format!("{} {}", mailbox.total, mailbox.size))
.await
}
}
+64
View File
@@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::network::SessionStream;
use crate::{
Session,
protocol::{Mechanism, response::Response},
};
pub mod authenticate;
pub mod delete;
pub mod fetch;
pub mod list;
impl<T: SessionStream> Session<T> {
pub async fn handle_capa(&mut self) -> trc::Result<()> {
let mechanisms = if self.stream.is_tls() || self.server.core.imap.allow_plain_auth {
vec![Mechanism::Plain, Mechanism::OAuthBearer, Mechanism::XOauth2]
} else {
vec![Mechanism::OAuthBearer, Mechanism::XOauth2]
};
trc::event!(
Pop3(trc::Pop3Event::Capabilities),
SpanId = self.session_id,
Tls = self.stream.is_tls(),
Strict = !self.server.core.imap.allow_plain_auth,
Elapsed = trc::Value::Duration(0)
);
self.write_bytes(
Response::Capability::<u32> {
mechanisms,
stls: !self.stream.is_tls(),
}
.serialize(),
)
.await
}
pub async fn handle_stls(&mut self) -> trc::Result<()> {
trc::event!(
Pop3(trc::Pop3Event::StartTls),
SpanId = self.session_id,
Elapsed = trc::Value::Duration(0)
);
self.write_ok("Begin TLS negotiation now").await
}
pub async fn handle_utf8(&mut self) -> trc::Result<()> {
trc::event!(
Pop3(trc::Pop3Event::Utf8),
SpanId = self.session_id,
Elapsed = trc::Value::Duration(0)
);
self.write_ok("UTF8 enabled").await
}
}