mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-09-08 13:04:26 +08:00
Removed authentication rate limit (unnecessary since there is fail2ban)
This commit is contained in:
parent
7a905ca137
commit
c7499ab67d
12 changed files with 115 additions and 146 deletions
|
@ -45,7 +45,6 @@ pub struct JmapConfig {
|
|||
|
||||
pub session_cache_ttl: Duration,
|
||||
pub rate_authenticated: Option<Rate>,
|
||||
pub rate_authenticate_req: Option<Rate>,
|
||||
pub rate_anonymous: Option<Rate>,
|
||||
|
||||
pub event_source_throttle: Duration,
|
||||
|
@ -305,9 +304,6 @@ impl JmapConfig {
|
|||
rate_authenticated: config
|
||||
.property_or_default::<Option<Rate>>("jmap.rate-limit.account", "1000/1m")
|
||||
.unwrap_or_default(),
|
||||
rate_authenticate_req: config
|
||||
.property_or_default::<Option<Rate>>("authentication.rate-limit", "10/1m")
|
||||
.unwrap_or_default(),
|
||||
rate_anonymous: config
|
||||
.property_or_default::<Option<Rate>>("jmap.rate-limit.anonymous", "100/1m")
|
||||
.unwrap_or_default(),
|
||||
|
|
|
@ -66,10 +66,9 @@ pub const KV_RATE_LIMIT_LOITER: u8 = 4;
|
|||
pub const KV_RATE_LIMIT_AUTH: u8 = 5;
|
||||
pub const KV_RATE_LIMIT_HASH: u8 = 6;
|
||||
pub const KV_RATE_LIMIT_CONTACT: u8 = 7;
|
||||
pub const KV_RATE_LIMIT_JMAP: u8 = 8;
|
||||
pub const KV_RATE_LIMIT_JMAP_AUTH: u8 = 9;
|
||||
pub const KV_RATE_LIMIT_HTTP_ANONYM: u8 = 10;
|
||||
pub const KV_RATE_LIMIT_IMAP: u8 = 11;
|
||||
pub const KV_RATE_LIMIT_HTTP_AUTHENTICATED: u8 = 8;
|
||||
pub const KV_RATE_LIMIT_HTTP_ANONYMOUS: u8 = 9;
|
||||
pub const KV_RATE_LIMIT_IMAP: u8 = 10;
|
||||
pub const KV_REPUTATION_IP: u8 = 12;
|
||||
pub const KV_REPUTATION_FROM: u8 = 13;
|
||||
pub const KV_REPUTATION_DOMAIN: u8 = 14;
|
||||
|
|
|
@ -17,7 +17,6 @@ use imap_proto::{
|
|||
receiver::{self, Request},
|
||||
Command, ResponseCode, StatusResponse,
|
||||
};
|
||||
use jmap::auth::rate_limit::RateLimiter;
|
||||
use mail_parser::decoders::base64::base64_decode;
|
||||
use mail_send::Credentials;
|
||||
use std::sync::Arc;
|
||||
|
@ -76,12 +75,6 @@ impl<T: SessionStream> Session<T> {
|
|||
credentials: Credentials<String>,
|
||||
tag: String,
|
||||
) -> trc::Result<()> {
|
||||
// Throttle authentication requests
|
||||
self.server
|
||||
.is_auth_allowed_soft(&self.remote_addr)
|
||||
.await
|
||||
.map_err(|err| err.id(tag.clone()))?;
|
||||
|
||||
// Authenticate
|
||||
let access_token = self
|
||||
.server
|
||||
|
|
|
@ -232,13 +232,15 @@ impl ParseHttp for Server {
|
|||
}
|
||||
("oauth-authorization-server", &Method::GET) => {
|
||||
// Limit anonymous requests
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return self.handle_oauth_metadata(req, session).await;
|
||||
}
|
||||
("openid-configuration", &Method::GET) => {
|
||||
// Limit anonymous requests
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return self.handle_oidc_metadata(req, session).await;
|
||||
}
|
||||
|
@ -258,6 +260,10 @@ impl ParseHttp for Server {
|
|||
}
|
||||
}
|
||||
("mta-sts.txt", &Method::GET) => {
|
||||
// Limit anonymous requests
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return if let Some(policy) = self.build_mta_sts_policy() {
|
||||
Ok(Resource::new("text/plain", policy.to_string().into_bytes())
|
||||
.into_http_response())
|
||||
|
@ -266,12 +272,20 @@ impl ParseHttp for Server {
|
|||
};
|
||||
}
|
||||
("mail-v1.xml", &Method::GET) => {
|
||||
// Limit anonymous requests
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return self.handle_autoconfig_request(&req).await;
|
||||
}
|
||||
("autoconfig", &Method::GET) => {
|
||||
if path.next().unwrap_or_default() == "mail"
|
||||
&& path.next().unwrap_or_default() == "config-v1.1.xml"
|
||||
{
|
||||
// Limit anonymous requests
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return self.handle_autoconfig_request(&req).await;
|
||||
}
|
||||
}
|
||||
|
@ -282,12 +296,14 @@ impl ParseHttp for Server {
|
|||
},
|
||||
"auth" => match (path.next().unwrap_or_default(), req.method()) {
|
||||
("device", &Method::POST) => {
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return self.handle_device_auth(&mut req, session).await;
|
||||
}
|
||||
("token", &Method::POST) => {
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return self.handle_token_request(&mut req, session).await;
|
||||
}
|
||||
|
@ -314,7 +330,8 @@ impl ParseHttp for Server {
|
|||
}
|
||||
("jwks.json", &Method::GET) => {
|
||||
// Limit anonymous requests
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return Ok(self.core.oauth.oidc_jwks.clone().into_http_response());
|
||||
}
|
||||
|
@ -408,6 +425,10 @@ impl ParseHttp for Server {
|
|||
if req.method() == Method::GET
|
||||
&& path.next().unwrap_or_default() == "config-v1.1.xml"
|
||||
{
|
||||
// Limit anonymous requests
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return self.handle_autoconfig_request(&req).await;
|
||||
}
|
||||
}
|
||||
|
@ -415,6 +436,10 @@ impl ParseHttp for Server {
|
|||
if req.method() == Method::POST
|
||||
&& path.next().unwrap_or_default() == "autodiscover.xml"
|
||||
{
|
||||
// Limit anonymous requests
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return self
|
||||
.handle_autodiscover_request(
|
||||
fetch_body(&mut req, 8192, session.session_id).await,
|
||||
|
@ -423,27 +448,37 @@ impl ParseHttp for Server {
|
|||
}
|
||||
}
|
||||
"robots.txt" => {
|
||||
// Limit anonymous requests
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return Ok(
|
||||
Resource::new("text/plain", b"User-agent: *\nDisallow: /\n".to_vec())
|
||||
.into_http_response(),
|
||||
);
|
||||
}
|
||||
"healthz" => match path.next().unwrap_or_default() {
|
||||
"live" => {
|
||||
return Ok(StatusCode::OK.into_http_response());
|
||||
}
|
||||
"ready" => {
|
||||
return Ok({
|
||||
if !self.core.storage.data.is_none() {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
}
|
||||
"healthz" => {
|
||||
// Limit anonymous requests
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
match path.next().unwrap_or_default() {
|
||||
"live" => {
|
||||
return Ok(StatusCode::OK.into_http_response());
|
||||
}
|
||||
.into_http_response());
|
||||
"ready" => {
|
||||
return Ok({
|
||||
if !self.core.storage.data.is_none() {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
}
|
||||
}
|
||||
.into_http_response());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
"metrics" => match path.next().unwrap_or_default() {
|
||||
"prometheus" => {
|
||||
if let Some(prometheus) = &self.core.metrics.prometheus {
|
||||
|
@ -508,7 +543,8 @@ impl ParseHttp for Server {
|
|||
if let Some(form) = &self.core.network.contact_form {
|
||||
match *req.method() {
|
||||
Method::POST => {
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
let form_data =
|
||||
FormData::from_request(&mut req, form.max_size, session.session_id)
|
||||
|
|
|
@ -144,9 +144,10 @@ impl ManageStore for Server {
|
|||
Some("rate-auth") => vec![KV_RATE_LIMIT_AUTH].into(),
|
||||
Some("rate-hash") => vec![KV_RATE_LIMIT_HASH].into(),
|
||||
Some("rate-contact") => vec![KV_RATE_LIMIT_CONTACT].into(),
|
||||
Some("rate-jmap") => vec![KV_RATE_LIMIT_JMAP].into(),
|
||||
Some("rate-jmap-auth") => vec![KV_RATE_LIMIT_JMAP_AUTH].into(),
|
||||
Some("rate-http-anonymous") => vec![KV_RATE_LIMIT_HTTP_ANONYM].into(),
|
||||
Some("rate-http-authenticated") => {
|
||||
vec![KV_RATE_LIMIT_HTTP_AUTHENTICATED].into()
|
||||
}
|
||||
Some("rate-http-anonymous") => vec![KV_RATE_LIMIT_HTTP_ANONYMOUS].into(),
|
||||
Some("rate-imap") => vec![KV_RATE_LIMIT_IMAP].into(),
|
||||
Some("reputation-ip") => vec![KV_REPUTATION_IP].into(),
|
||||
Some("reputation-from") => vec![KV_REPUTATION_FROM].into(),
|
||||
|
|
|
@ -41,9 +41,6 @@ impl Authenticator for Server {
|
|||
self.get_cached_access_token(account_id).await?
|
||||
} else {
|
||||
let credentials = if mechanism.eq_ignore_ascii_case("basic") {
|
||||
// Throttle authentication requests
|
||||
self.is_auth_allowed_soft(&session.remote_ip).await?;
|
||||
|
||||
// Decode the base64 encoded credentials
|
||||
decode_plain_auth(token).ok_or_else(|| {
|
||||
trc::AuthEvent::Error
|
||||
|
@ -54,7 +51,8 @@ impl Authenticator for Server {
|
|||
})?
|
||||
} else if mechanism.eq_ignore_ascii_case("bearer") {
|
||||
// Enforce anonymous rate limit
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
decode_bearer_token(token, allow_api_access).ok_or_else(|| {
|
||||
trc::AuthEvent::Error
|
||||
|
@ -65,7 +63,8 @@ impl Authenticator for Server {
|
|||
})?
|
||||
} else {
|
||||
// Enforce anonymous rate limit
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
return Err(trc::AuthEvent::Error
|
||||
.into_err()
|
||||
|
@ -75,22 +74,13 @@ impl Authenticator for Server {
|
|||
};
|
||||
|
||||
// Authenticate
|
||||
let access_token = match self
|
||||
let access_token = self
|
||||
.authenticate(&AuthRequest::from_credentials(
|
||||
credentials,
|
||||
session.session_id,
|
||||
session.remote_ip,
|
||||
))
|
||||
.await
|
||||
{
|
||||
Ok(access_token) => access_token,
|
||||
Err(err) => {
|
||||
if err.matches(trc::EventType::Auth(trc::AuthEvent::Failed)) {
|
||||
let _ = self.is_auth_allowed_hard(&session.remote_ip).await;
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
.await?;
|
||||
|
||||
// Cache session
|
||||
self.cache_session(token.to_string(), &access_token);
|
||||
|
@ -98,12 +88,13 @@ impl Authenticator for Server {
|
|||
};
|
||||
|
||||
// Enforce authenticated rate limit
|
||||
self.is_account_allowed(&access_token)
|
||||
self.is_http_authenticated_request_allowed(&access_token)
|
||||
.await
|
||||
.map(|in_flight| (in_flight, access_token))
|
||||
} else {
|
||||
// Enforce anonymous rate limit
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
|
||||
Err(trc::AuthEvent::Failed
|
||||
.into_err()
|
||||
|
|
|
@ -54,7 +54,8 @@ impl ClientRegistrationHandler for Server {
|
|||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::OauthClientRegistration)?;
|
||||
} else {
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
self.is_http_anonymous_request_allowed(&session.remote_ip)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Parse request
|
||||
|
|
|
@ -9,8 +9,7 @@ use std::{net::IpAddr, sync::Arc};
|
|||
use common::{
|
||||
ip_to_bytes,
|
||||
listener::limiter::{ConcurrencyLimiter, InFlight},
|
||||
ConcurrencyLimiters, Server, KV_RATE_LIMIT_HTTP_ANONYM, KV_RATE_LIMIT_JMAP,
|
||||
KV_RATE_LIMIT_JMAP_AUTH,
|
||||
ConcurrencyLimiters, Server, KV_RATE_LIMIT_HTTP_ANONYMOUS, KV_RATE_LIMIT_HTTP_AUTHENTICATED,
|
||||
};
|
||||
use directory::Permission;
|
||||
use trc::AddContext;
|
||||
|
@ -20,14 +19,15 @@ use std::future::Future;
|
|||
|
||||
pub trait RateLimiter: Sync + Send {
|
||||
fn get_concurrency_limiter(&self, account_id: u32) -> Arc<ConcurrencyLimiters>;
|
||||
fn is_account_allowed(
|
||||
fn is_http_authenticated_request_allowed(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
) -> impl Future<Output = trc::Result<InFlight>> + Send;
|
||||
fn is_anonymous_allowed(&self, addr: &IpAddr) -> impl Future<Output = trc::Result<()>> + Send;
|
||||
fn is_http_anonymous_request_allowed(
|
||||
&self,
|
||||
addr: &IpAddr,
|
||||
) -> impl Future<Output = trc::Result<()>> + Send;
|
||||
fn is_upload_allowed(&self, access_token: &AccessToken) -> trc::Result<InFlight>;
|
||||
fn is_auth_allowed_soft(&self, addr: &IpAddr) -> impl Future<Output = trc::Result<()>> + Send;
|
||||
fn is_auth_allowed_hard(&self, addr: &IpAddr) -> impl Future<Output = trc::Result<()>> + Send;
|
||||
}
|
||||
|
||||
impl RateLimiter for Server {
|
||||
|
@ -54,14 +54,17 @@ impl RateLimiter for Server {
|
|||
})
|
||||
}
|
||||
|
||||
async fn is_account_allowed(&self, access_token: &AccessToken) -> trc::Result<InFlight> {
|
||||
async fn is_http_authenticated_request_allowed(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
) -> trc::Result<InFlight> {
|
||||
let limiter = self.get_concurrency_limiter(access_token.primary_id());
|
||||
let is_rate_allowed = if let Some(rate) = &self.core.jmap.rate_authenticated {
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(
|
||||
KV_RATE_LIMIT_JMAP,
|
||||
KV_RATE_LIMIT_HTTP_AUTHENTICATED,
|
||||
&access_token.primary_id.to_be_bytes(),
|
||||
rate,
|
||||
false,
|
||||
|
@ -88,13 +91,18 @@ impl RateLimiter for Server {
|
|||
}
|
||||
}
|
||||
|
||||
async fn is_anonymous_allowed(&self, addr: &IpAddr) -> trc::Result<()> {
|
||||
async fn is_http_anonymous_request_allowed(&self, addr: &IpAddr) -> trc::Result<()> {
|
||||
if let Some(rate) = &self.core.jmap.rate_anonymous {
|
||||
if self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(KV_RATE_LIMIT_HTTP_ANONYM, &ip_to_bytes(addr), rate, false)
|
||||
.is_rate_allowed(
|
||||
KV_RATE_LIMIT_HTTP_ANONYMOUS,
|
||||
&ip_to_bytes(addr),
|
||||
rate,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
|
@ -118,38 +126,4 @@ impl RateLimiter for Server {
|
|||
Err(trc::LimitEvent::ConcurrentUpload.into_err())
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_auth_allowed_soft(&self, addr: &IpAddr) -> trc::Result<()> {
|
||||
if let Some(rate) = &self.core.jmap.rate_authenticate_req {
|
||||
if self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(KV_RATE_LIMIT_JMAP_AUTH, &ip_to_bytes(addr), rate, true)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::AuthEvent::TooManyAttempts.into_err());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn is_auth_allowed_hard(&self, addr: &IpAddr) -> trc::Result<()> {
|
||||
if let Some(rate) = &self.core.jmap.rate_authenticate_req {
|
||||
if self
|
||||
.core
|
||||
.storage
|
||||
.lookup
|
||||
.is_rate_allowed(KV_RATE_LIMIT_JMAP_AUTH, &ip_to_bytes(addr), rate, false)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(trc::AuthEvent::TooManyAttempts.into_err());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ use imap_proto::{
|
|||
protocol::authenticate::Mechanism,
|
||||
receiver::{self, Request},
|
||||
};
|
||||
use jmap::auth::rate_limit::RateLimiter;
|
||||
use mail_parser::decoders::base64::base64_decode;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -71,9 +70,6 @@ impl<T: SessionStream> Session<T> {
|
|||
}
|
||||
};
|
||||
|
||||
// Throttle authentication requests
|
||||
self.server.is_auth_allowed_soft(&self.remote_addr).await?;
|
||||
|
||||
// Authenticate
|
||||
let access_token = self
|
||||
.server
|
||||
|
|
|
@ -13,7 +13,6 @@ use common::{
|
|||
ConcurrencyLimiters,
|
||||
};
|
||||
use directory::Permission;
|
||||
use jmap::auth::rate_limit::RateLimiter;
|
||||
use mail_parser::decoders::base64::base64_decode;
|
||||
use mail_send::Credentials;
|
||||
use std::sync::Arc;
|
||||
|
@ -68,9 +67,6 @@ impl<T: SessionStream> Session<T> {
|
|||
}
|
||||
|
||||
pub async fn handle_auth(&mut self, credentials: Credentials<String>) -> trc::Result<()> {
|
||||
// Throttle authentication requests
|
||||
self.server.is_auth_allowed_soft(&self.remote_addr).await?;
|
||||
|
||||
// Authenticate
|
||||
let access_token = self
|
||||
.server
|
||||
|
|
|
@ -68,40 +68,6 @@ pub async fn test(params: &mut JMAPTest) {
|
|||
let range_end = (range_start * LIMIT) + LIMIT;
|
||||
tokio::time::sleep(Duration::from_secs(range_end - now)).await;
|
||||
|
||||
// Invalid authentication requests should be rate limited
|
||||
let mut n_401 = 0;
|
||||
let mut n_429 = 0;
|
||||
for n in 0..110 {
|
||||
if let Err(jmap_client::Error::Problem(problem)) = Client::new()
|
||||
.credentials(Credentials::basic(
|
||||
"not_an_account@example.com",
|
||||
&format!("brute_force{}", n),
|
||||
))
|
||||
.accept_invalid_certs(true)
|
||||
.connect("https://127.0.0.1:8899")
|
||||
.await
|
||||
{
|
||||
if problem.status().unwrap() == 401 {
|
||||
n_401 += 1;
|
||||
if n_401 > 100 {
|
||||
panic!("Rate limiter failed: 429: {n_429}, 401: {n_401}.");
|
||||
}
|
||||
} else if problem.status().unwrap() == 429 {
|
||||
n_429 += 1;
|
||||
if n_429 > 11 {
|
||||
panic!("Rate limiter too restrictive: 429: {n_429}, 401: {n_401}.");
|
||||
}
|
||||
} else {
|
||||
panic!("Unexpected error status {}", problem.status().unwrap());
|
||||
}
|
||||
} else {
|
||||
panic!("Unexpected response.");
|
||||
}
|
||||
}
|
||||
|
||||
// Limit should be restored after 1 second
|
||||
tokio::time::sleep(Duration::from_millis(1500)).await;
|
||||
|
||||
// Test fail2ban
|
||||
assert_eq!(
|
||||
server
|
||||
|
@ -113,6 +79,26 @@ pub async fn test(params: &mut JMAPTest) {
|
|||
.unwrap(),
|
||||
None
|
||||
);
|
||||
for n in 0..98 {
|
||||
match Client::new()
|
||||
.credentials(Credentials::basic(
|
||||
"not_an_account@example.com",
|
||||
&format!("brute_force{}", n),
|
||||
))
|
||||
.accept_invalid_certs(true)
|
||||
.connect("https://127.0.0.1:8899")
|
||||
.await
|
||||
{
|
||||
Err(jmap_client::Error::Problem(_)) => {}
|
||||
Err(err) => {
|
||||
panic!("Unexpected response: {:?}", err);
|
||||
}
|
||||
Ok(_) => {
|
||||
panic!("Unexpected success");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut imap = ImapConnection::connect(b"_x ").await;
|
||||
imap.send("AUTHENTICATE PLAIN AGpvaG4AY2hpbWljaGFuZ2Fz")
|
||||
.await;
|
||||
|
|
|
@ -120,7 +120,7 @@ implicit = false
|
|||
certificate = "default"
|
||||
|
||||
[server.fail2ban]
|
||||
authentication = "101/5s"
|
||||
authentication = "100/5s"
|
||||
|
||||
[authentication]
|
||||
rate-limit = "100/2s"
|
||||
|
@ -371,7 +371,7 @@ pub async fn jmap_tests() {
|
|||
.await;
|
||||
|
||||
webhooks::test(&mut params).await;
|
||||
/*email_query::test(&mut params, delete).await;
|
||||
email_query::test(&mut params, delete).await;
|
||||
email_get::test(&mut params).await;
|
||||
email_set::test(&mut params).await;
|
||||
email_parse::test(&mut params).await;
|
||||
|
@ -381,7 +381,7 @@ pub async fn jmap_tests() {
|
|||
email_copy::test(&mut params).await;
|
||||
thread_get::test(&mut params).await;
|
||||
thread_merge::test(&mut params).await;
|
||||
mailbox::test(&mut params).await;*/
|
||||
mailbox::test(&mut params).await;
|
||||
delivery::test(&mut params).await;
|
||||
auth_acl::test(&mut params).await;
|
||||
auth_limits::test(&mut params).await;
|
||||
|
|
Loading…
Add table
Reference in a new issue