mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-12-10 05:16:35 +08:00
Always use rsplit to extract the domain part from email addresses
This commit is contained in:
parent
d00d3dd013
commit
5fcf73f070
14 changed files with 87 additions and 96 deletions
|
|
@ -13,6 +13,7 @@ use store::{
|
|||
write::{DirectoryClass, ValueClass},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use utils::DomainPart;
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait DirectoryStore: Sync + Send {
|
||||
|
|
@ -117,7 +118,7 @@ impl DirectoryStore for Store {
|
|||
|
||||
async fn vrfy(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
let mut results = Vec::new();
|
||||
let address = address.split('@').next().unwrap_or(address);
|
||||
let address = address.try_local_part().unwrap_or(address);
|
||||
if address.len() > 3 {
|
||||
self.iterate(
|
||||
IterateParams::new(
|
||||
|
|
@ -129,7 +130,7 @@ impl DirectoryStore for Store {
|
|||
|key, value| {
|
||||
let key =
|
||||
std::str::from_utf8(key.get(1..).unwrap_or_default()).unwrap_or_default();
|
||||
if key.split('@').next().unwrap_or(key).contains(address)
|
||||
if key.try_local_part().unwrap_or(key).contains(address)
|
||||
&& PrincipalInfo::deserialize(value)
|
||||
.caused_by(trc::location!())?
|
||||
.typ
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ use store::{
|
|||
};
|
||||
use trc::AddContext;
|
||||
use types::collection::Collection;
|
||||
use utils::sanitize_email;
|
||||
use utils::{DomainPart, sanitize_email};
|
||||
|
||||
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PrincipalList<T> {
|
||||
|
|
@ -354,7 +354,7 @@ impl ManageDirectory for Store {
|
|||
principal_create.tenant = tenant_id.into();
|
||||
|
||||
if !matches!(principal_create.typ, Type::Tenant | Type::Domain) {
|
||||
if let Some(domain) = name.split('@').nth(1)
|
||||
if let Some(domain) = name.try_domain_part()
|
||||
&& self
|
||||
.get_principal_info(domain)
|
||||
.await
|
||||
|
|
@ -523,7 +523,7 @@ impl ManageDirectory for Store {
|
|||
if self.rcpt(&email).await.caused_by(trc::location!())? != RcptType::Invalid {
|
||||
return Err(err_exists(PrincipalField::Emails, email.to_string()));
|
||||
}
|
||||
if let Some(domain) = email.split('@').nth(1)
|
||||
if let Some(domain) = email.try_domain_part()
|
||||
&& valid_domains.insert(domain.into())
|
||||
{
|
||||
self.get_principal_info(domain)
|
||||
|
|
@ -1003,7 +1003,7 @@ impl ManageDirectory for Store {
|
|||
if tenant_id.is_some()
|
||||
&& !matches!(principal_type, Type::Tenant | Type::Domain)
|
||||
{
|
||||
if let Some(domain) = new_name.split('@').nth(1)
|
||||
if let Some(domain) = new_name.try_domain_part()
|
||||
&& self
|
||||
.get_principal_info(domain)
|
||||
.await
|
||||
|
|
@ -2401,7 +2401,7 @@ impl ValidateDirectory for Store {
|
|||
) -> trc::Result<()> {
|
||||
if self.rcpt(email).await.caused_by(trc::location!())? != RcptType::Invalid {
|
||||
Err(err_exists(PrincipalField::Emails, email.to_string()))
|
||||
} else if let Some(domain) = email.split('@').nth(1) {
|
||||
} else if let Some(domain) = email.try_domain_part() {
|
||||
match self
|
||||
.get_principal_info(domain)
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ impl EmailAddress<'_> {
|
|||
&self.address
|
||||
};
|
||||
|
||||
if let Some((local, host)) = addr.split_once('@') {
|
||||
if let Some((local, host)) = addr.rsplit_once('@') {
|
||||
quoted_or_literal_string(buf, local);
|
||||
buf.push(b' ');
|
||||
quoted_or_literal_string(buf, host);
|
||||
|
|
|
|||
|
|
@ -4,31 +4,26 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{
|
||||
hash::Hash,
|
||||
net::IpAddr,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{inbound::auth::SaslToken, queue::QueueId};
|
||||
use common::{
|
||||
Inner, Server,
|
||||
auth::AccessToken,
|
||||
config::smtp::auth::VerifyStrategy,
|
||||
listener::{ServerInstance, asn::AsnGeoLookupResult},
|
||||
};
|
||||
|
||||
use directory::Directory;
|
||||
use mail_auth::{IprevOutput, SpfOutput};
|
||||
use smtp_proto::request::receiver::{
|
||||
BdatReceiver, DataReceiver, DummyDataReceiver, DummyLineReceiver, LineReceiver, RequestReceiver,
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use crate::{
|
||||
inbound::auth::SaslToken,
|
||||
queue::{DomainPart, QueueId},
|
||||
use std::{
|
||||
hash::Hash,
|
||||
net::IpAddr,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use utils::DomainPart;
|
||||
|
||||
pub mod params;
|
||||
pub mod throttle;
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ use super::{ArcSeal, AuthResult, DkimSign};
|
|||
use crate::{
|
||||
core::{Session, SessionAddress, State},
|
||||
inbound::milter::Modification,
|
||||
queue::{
|
||||
self, DomainPart, Message, MessageSource, MessageWrapper, QueueEnvelope,
|
||||
quota::HasQueueQuota,
|
||||
},
|
||||
queue::{self, Message, MessageSource, MessageWrapper, QueueEnvelope, quota::HasQueueQuota},
|
||||
reporting::analysis::AnalyzeReport,
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
|
|
@ -44,7 +41,7 @@ use std::{
|
|||
time::{Instant, SystemTime},
|
||||
};
|
||||
use trc::SmtpEvent;
|
||||
use utils::config::Rate;
|
||||
use utils::{DomainPart, config::Rate};
|
||||
|
||||
impl<T: SessionStream> Session<T> {
|
||||
pub async fn queue_message(&mut self) -> Cow<'static, [u8]> {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
use crate::{
|
||||
core::{Session, SessionAddress},
|
||||
queue::DomainPart,
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
use common::{config::smtp::session::Stage, listener::SessionStream, scripts::ScriptModification};
|
||||
|
|
@ -17,7 +16,7 @@ use std::{
|
|||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
use trc::SmtpEvent;
|
||||
use utils::config::Rate;
|
||||
use utils::{DomainPart, config::Rate};
|
||||
|
||||
impl<T: SessionStream> Session<T> {
|
||||
pub async fn handle_mail_from(&mut self, from: MailFrom<Cow<'_, str>>) -> Result<(), ()> {
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{borrow::Cow, time::Instant};
|
||||
|
||||
use super::{Action, Error, Macros, Modification};
|
||||
use crate::{
|
||||
core::{Session, SessionAddress, SessionData},
|
||||
inbound::{FilterResponse, milter::MilterClient},
|
||||
};
|
||||
use common::{
|
||||
DAEMON_NAME,
|
||||
config::smtp::session::{Milter, Stage},
|
||||
listener::SessionStream,
|
||||
};
|
||||
|
||||
use mail_auth::AuthenticatedMessage;
|
||||
use smtp_proto::{IntoString, request::parser::Rfc5321Parser};
|
||||
use std::{borrow::Cow, time::Instant};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use trc::MilterEvent;
|
||||
|
||||
use crate::{
|
||||
core::{Session, SessionAddress, SessionData},
|
||||
inbound::{FilterResponse, milter::MilterClient},
|
||||
queue::DomainPart,
|
||||
};
|
||||
|
||||
use super::{Action, Error, Macros, Modification};
|
||||
use utils::DomainPart;
|
||||
|
||||
enum Rejection {
|
||||
Action(Action),
|
||||
|
|
|
|||
|
|
@ -4,24 +4,21 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{
|
||||
core::{Session, SessionAddress},
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
use common::{
|
||||
KV_GREYLIST, config::smtp::session::Stage, listener::SessionStream, scripts::ScriptModification,
|
||||
};
|
||||
|
||||
use directory::backend::RcptType;
|
||||
use smtp_proto::{
|
||||
RCPT_NOTIFY_DELAY, RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_NEVER, RCPT_NOTIFY_SUCCESS, RcptTo,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use store::dispatch::lookup::KeyValue;
|
||||
use trc::{SecurityEvent, SmtpEvent};
|
||||
|
||||
use crate::{
|
||||
core::{Session, SessionAddress},
|
||||
queue::DomainPart,
|
||||
scripts::ScriptResult,
|
||||
};
|
||||
use utils::DomainPart;
|
||||
|
||||
impl<T: SessionStream> Session<T> {
|
||||
pub async fn handle_rcpt_to(&mut self, to: RcptTo<Cow<'_, str>>) -> Result<(), ()> {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use std::{
|
|||
};
|
||||
use store::write::now;
|
||||
use types::blob_hash::BlobHash;
|
||||
use utils::DomainPart;
|
||||
|
||||
pub mod dsn;
|
||||
pub mod manager;
|
||||
|
|
@ -460,38 +461,6 @@ impl InstantFromTimestamp for u64 {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait DomainPart {
|
||||
fn to_lowercase_domain(&self) -> String;
|
||||
fn domain_part(&self) -> &str;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> DomainPart for T {
|
||||
fn to_lowercase_domain(&self) -> String {
|
||||
let address = self.as_ref();
|
||||
if let Some((local, domain)) = address.rsplit_once('@') {
|
||||
let mut address = String::with_capacity(address.len());
|
||||
address.push_str(local);
|
||||
address.push('@');
|
||||
for ch in domain.chars() {
|
||||
for ch in ch.to_lowercase() {
|
||||
address.push(ch);
|
||||
}
|
||||
}
|
||||
address
|
||||
} else {
|
||||
address.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn domain_part(&self) -> &str {
|
||||
self.as_ref()
|
||||
.rsplit_once('@')
|
||||
.map(|(_, d)| d)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,7 @@
|
|||
*/
|
||||
|
||||
use super::{QueueEnvelope, QuotaKey, Status};
|
||||
use crate::{
|
||||
core::throttle::NewKey,
|
||||
queue::{DomainPart, MessageWrapper},
|
||||
};
|
||||
use crate::{core::throttle::NewKey, queue::MessageWrapper};
|
||||
use ahash::AHashSet;
|
||||
use common::{Server, config::smtp::queue::QueueQuota, expr::functions::ResolveVariable};
|
||||
use std::future::Future;
|
||||
|
|
@ -17,6 +14,7 @@ use store::{
|
|||
write::{BatchBuilder, QueueClass, ValueClass},
|
||||
};
|
||||
use trc::QueueEvent;
|
||||
use utils::DomainPart;
|
||||
|
||||
pub trait HasQueueQuota: Sync + Send {
|
||||
fn has_quota(&self, message: &mut MessageWrapper) -> impl Future<Output = bool> + Send;
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ use super::{
|
|||
};
|
||||
use crate::queue::manager::{LockedMessage, Queue};
|
||||
use crate::queue::{
|
||||
DomainPart, FROM_AUTHENTICATED, FROM_AUTOGENERATED, FROM_DSN, FROM_REPORT,
|
||||
FROM_UNAUTHENTICATED, FROM_UNAUTHENTICATED_DMARC, MessageWrapper,
|
||||
FROM_AUTHENTICATED, FROM_AUTOGENERATED, FROM_DSN, FROM_REPORT, FROM_UNAUTHENTICATED,
|
||||
FROM_UNAUTHENTICATED_DMARC, MessageWrapper,
|
||||
};
|
||||
use common::config::smtp::queue::QueueName;
|
||||
use common::ipc::QueueEvent;
|
||||
|
|
@ -28,6 +28,7 @@ use store::write::{
|
|||
use store::{Deserialize, IterateParams, Serialize, SerializeInfallible, U64_LEN, ValueKey};
|
||||
use trc::{AddContext, ServerEvent};
|
||||
use types::blob_hash::BlobHash;
|
||||
use utils::DomainPart;
|
||||
|
||||
pub const LOCK_EXPIRY: u64 = 10 * 60; // 10 minutes
|
||||
pub const QUEUE_REFRESH: u64 = 5 * 60; // 5 minutes
|
||||
|
|
|
|||
|
|
@ -5,11 +5,7 @@
|
|||
*/
|
||||
|
||||
use super::{AggregateTimestamp, SerializedSize};
|
||||
use crate::{
|
||||
core::Session,
|
||||
queue::{DomainPart, RecipientDomain},
|
||||
reporting::SmtpReporting,
|
||||
};
|
||||
use crate::{core::Session, queue::RecipientDomain, reporting::SmtpReporting};
|
||||
use ahash::AHashMap;
|
||||
use common::{
|
||||
Server,
|
||||
|
|
@ -31,7 +27,7 @@ use store::{
|
|||
write::{AlignedBytes, Archive, Archiver, BatchBuilder, QueueClass, ReportEvent, ValueClass},
|
||||
};
|
||||
use trc::{AddContext, OutgoingReportEvent};
|
||||
use utils::config::Rate;
|
||||
use utils::{DomainPart, config::Rate};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
|
|
|
|||
|
|
@ -9,11 +9,9 @@ use smtp_proto::{
|
|||
MAIL_BY_NOTIFY, MAIL_BY_RETURN, MAIL_BY_TRACE, MAIL_RET_FULL, MAIL_RET_HDRS, RCPT_NOTIFY_DELAY,
|
||||
RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_NEVER, RCPT_NOTIFY_SUCCESS,
|
||||
};
|
||||
use utils::DomainPart;
|
||||
|
||||
use crate::{
|
||||
core::{SessionAddress, SessionData},
|
||||
queue::DomainPart,
|
||||
};
|
||||
use crate::core::{SessionAddress, SessionData};
|
||||
|
||||
impl SessionData {
|
||||
pub fn apply_envelope_modification(&mut self, envelope: Envelope, value: String) {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,50 @@ pub fn rustls_client_config(allow_invalid_certs: bool) -> ClientConfig {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait DomainPart {
|
||||
fn to_lowercase_domain(&self) -> String;
|
||||
fn domain_part(&self) -> &str;
|
||||
fn try_domain_part(&self) -> Option<&str>;
|
||||
fn try_local_part(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> DomainPart for T {
|
||||
fn to_lowercase_domain(&self) -> String {
|
||||
let address = self.as_ref();
|
||||
if let Some((local, domain)) = address.rsplit_once('@') {
|
||||
let mut address = String::with_capacity(address.len());
|
||||
address.push_str(local);
|
||||
address.push('@');
|
||||
for ch in domain.chars() {
|
||||
for ch in ch.to_lowercase() {
|
||||
address.push(ch);
|
||||
}
|
||||
}
|
||||
address
|
||||
} else {
|
||||
address.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_domain_part(&self) -> Option<&str> {
|
||||
self.as_ref().rsplit_once('@').map(|(_, d)| d)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_local_part(&self) -> Option<&str> {
|
||||
self.as_ref().rsplit_once('@').map(|(l, _)| l)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn domain_part(&self) -> &str {
|
||||
self.as_ref()
|
||||
.rsplit_once('@')
|
||||
.map(|(_, d)| d)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DummyVerifier;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue