mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-10-09 20:15:47 +08:00
Delivery and DMARC Troubleshooting (closes #420)
This commit is contained in:
parent
f8f33848e4
commit
a6f24d23b4
16 changed files with 1165 additions and 50 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1,6 +1,6 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "RustyXML"
|
name = "RustyXML"
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://discord.gg/jtgtCNj66U"><img src="https://img.shields.io/discord/923615863037390889?label=Join%20Discord&logo=discord&style=flat-square" alt="Discord"></a>
|
<a href="https://discord.gg/jtgtCNj66U"><img src="https://img.shields.io/discord/923615863037390889?label=Join%20Discord&logo=discord&style=flat-square" alt="Discord"></a>
|
||||||
|
|
||||||
<a href="https://www.reddit.com/r/stalwartlabs/"><img src="https://img.shields.io/reddit/subreddit-subscribers/stalwartlabs?label=Follow%20%2Fr%2Fstalwartlabs&logo=reddit&style=flat-square" alt="Reddit"></a>
|
<a href="https://www.reddit.com/r/stalwartlabs/"><img src="https://img.shields.io/reddit/subreddit-subscribers/stalwartlabs?label=Join%20%2Fr%2Fstalwartlabs&logo=reddit&style=flat-square" alt="Reddit"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
|
@ -24,6 +24,7 @@ pub enum GrantType {
|
||||||
RefreshToken,
|
RefreshToken,
|
||||||
LiveTracing,
|
LiveTracing,
|
||||||
LiveMetrics,
|
LiveMetrics,
|
||||||
|
Troubleshoot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GrantType {
|
impl GrantType {
|
||||||
|
@ -33,6 +34,7 @@ impl GrantType {
|
||||||
GrantType::RefreshToken => "refresh_token",
|
GrantType::RefreshToken => "refresh_token",
|
||||||
GrantType::LiveTracing => "live_tracing",
|
GrantType::LiveTracing => "live_tracing",
|
||||||
GrantType::LiveMetrics => "live_metrics",
|
GrantType::LiveMetrics => "live_metrics",
|
||||||
|
GrantType::Troubleshoot => "troubleshoot",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +44,7 @@ impl GrantType {
|
||||||
GrantType::RefreshToken => 1,
|
GrantType::RefreshToken => 1,
|
||||||
GrantType::LiveTracing => 2,
|
GrantType::LiveTracing => 2,
|
||||||
GrantType::LiveMetrics => 3,
|
GrantType::LiveMetrics => 3,
|
||||||
|
GrantType::Troubleshoot => 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +54,7 @@ impl GrantType {
|
||||||
1 => Some(GrantType::RefreshToken),
|
1 => Some(GrantType::RefreshToken),
|
||||||
2 => Some(GrantType::LiveTracing),
|
2 => Some(GrantType::LiveTracing),
|
||||||
3 => Some(GrantType::LiveMetrics),
|
3 => Some(GrantType::LiveMetrics),
|
||||||
|
4 => Some(GrantType::Troubleshoot),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ use mail_auth::{
|
||||||
Resolver,
|
Resolver,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use utils::config::{utils::ParseValue, Config};
|
use utils::config::{utils::ParseValue, Config};
|
||||||
|
|
||||||
use crate::Server;
|
use crate::Server;
|
||||||
|
@ -42,7 +43,7 @@ pub struct DnsRecordCache {
|
||||||
pub mta_sts: LruCache<String, Arc<Policy>>,
|
pub mta_sts: LruCache<String, Arc<Policy>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct TlsaEntry {
|
pub struct TlsaEntry {
|
||||||
pub is_end_entity: bool,
|
pub is_end_entity: bool,
|
||||||
pub is_sha256: bool,
|
pub is_sha256: bool,
|
||||||
|
@ -50,14 +51,15 @@ pub struct TlsaEntry {
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Tlsa {
|
pub struct Tlsa {
|
||||||
pub entries: Vec<TlsaEntry>,
|
pub entries: Vec<TlsaEntry>,
|
||||||
pub has_end_entities: bool,
|
pub has_end_entities: bool,
|
||||||
pub has_intermediates: bool,
|
pub has_intermediates: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Enforce,
|
Enforce,
|
||||||
Testing,
|
Testing,
|
||||||
|
@ -65,13 +67,14 @@ pub enum Mode {
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
|
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum MxPattern {
|
pub enum MxPattern {
|
||||||
Equals(String),
|
Equals(String),
|
||||||
StartsWith(String),
|
StartsWith(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
|
||||||
pub struct Policy {
|
pub struct Policy {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
|
|
|
@ -198,6 +198,7 @@ impl Permission {
|
||||||
Permission::OauthClientUpdate => "Modify OAuth clients",
|
Permission::OauthClientUpdate => "Modify OAuth clients",
|
||||||
Permission::OauthClientDelete => "Remove OAuth clients",
|
Permission::OauthClientDelete => "Remove OAuth clients",
|
||||||
Permission::AiModelInteract => "Interact with AI models",
|
Permission::AiModelInteract => "Interact with AI models",
|
||||||
|
Permission::Troubleshoot => "Perform troubleshooting",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -264,7 +264,7 @@ pub enum Permission {
|
||||||
OauthClientOverride,
|
OauthClientOverride,
|
||||||
|
|
||||||
AiModelInteract,
|
AiModelInteract,
|
||||||
// WARNING: add new ids at the end (TODO: use static ids)
|
Troubleshoot, // WARNING: add new ids at the end (TODO: use static ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Permissions = Bitset<{ Permission::COUNT.div_ceil(std::mem::size_of::<usize>()) }>;
|
pub type Permissions = Bitset<{ Permission::COUNT.div_ceil(std::mem::size_of::<usize>()) }>;
|
||||||
|
|
|
@ -33,6 +33,7 @@ use jmap_proto::{
|
||||||
};
|
};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use trc::SecurityEvent;
|
use trc::SecurityEvent;
|
||||||
|
use utils::url_params::UrlParams;
|
||||||
|
|
||||||
#[cfg(feature = "enterprise")]
|
#[cfg(feature = "enterprise")]
|
||||||
use crate::api::management::enterprise::telemetry::TelemetryApi;
|
use crate::api::management::enterprise::telemetry::TelemetryApi;
|
||||||
|
@ -54,7 +55,7 @@ use super::{
|
||||||
autoconfig::Autoconfig,
|
autoconfig::Autoconfig,
|
||||||
event_source::EventSourceHandler,
|
event_source::EventSourceHandler,
|
||||||
form::FormHandler,
|
form::FormHandler,
|
||||||
management::{ManagementApi, ManagementApiError},
|
management::{troubleshoot::TroubleshootApi, ManagementApi, ManagementApiError},
|
||||||
request::RequestHandler,
|
request::RequestHandler,
|
||||||
session::SessionHandler,
|
session::SessionHandler,
|
||||||
HtmlResponse, HttpRequest, HttpResponse, HttpResponseBody, JmapSessionManager, JsonResponse,
|
HtmlResponse, HttpRequest, HttpResponse, HttpResponseBody, JmapSessionManager, JsonResponse,
|
||||||
|
@ -335,45 +336,67 @@ impl ParseHttp for Server {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
#[cfg(feature = "enterprise")]
|
if err.matches(trc::EventType::Auth(trc::AuthEvent::Failed)) {
|
||||||
{
|
let params = UrlParams::new(req.uri().query());
|
||||||
|
let path = req.uri().path().split('/').skip(2).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let (grant_type, token) = match (
|
||||||
|
path.first().copied(),
|
||||||
|
path.get(1).copied(),
|
||||||
|
params.get("token"),
|
||||||
|
) {
|
||||||
// SPDX-SnippetBegin
|
// SPDX-SnippetBegin
|
||||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||||
// SPDX-License-Identifier: LicenseRef-SEL
|
// SPDX-License-Identifier: LicenseRef-SEL
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
// Eventsource does not support authentication, validate the token instead
|
(Some("telemetry"), Some("traces"), Some(token))
|
||||||
if err.matches(trc::EventType::Auth(trc::AuthEvent::Failed))
|
if self.core.is_enterprise_edition() =>
|
||||||
&& self.core.is_enterprise_edition()
|
|
||||||
{
|
{
|
||||||
if let Some((live_path, grant_type, token)) = req
|
(GrantType::LiveTracing, token)
|
||||||
.uri()
|
}
|
||||||
.path()
|
#[cfg(feature = "enterprise")]
|
||||||
.strip_prefix("/api/telemetry/")
|
(Some("telemetry"), Some("metrics"), Some(token))
|
||||||
.and_then(|p| {
|
if self.core.is_enterprise_edition() =>
|
||||||
p.strip_prefix("traces/live/")
|
|
||||||
.map(|t| ("traces", GrantType::LiveTracing, t))
|
|
||||||
.or_else(|| {
|
|
||||||
p.strip_prefix("metrics/live/")
|
|
||||||
.map(|t| ("metrics", GrantType::LiveMetrics, t))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
let token_info = self
|
(GrantType::LiveMetrics, token)
|
||||||
.validate_access_token(grant_type.into(), token)
|
}
|
||||||
.await?;
|
// SPDX-SnippetEnd
|
||||||
|
(Some("troubleshoot"), _, Some(token)) => {
|
||||||
|
(GrantType::Troubleshoot, token)
|
||||||
|
}
|
||||||
|
_ => return Err(err),
|
||||||
|
};
|
||||||
|
let token_info =
|
||||||
|
self.validate_access_token(grant_type.into(), token).await?;
|
||||||
|
|
||||||
return self
|
return match grant_type {
|
||||||
.handle_telemetry_api_request(
|
// SPDX-SnippetBegin
|
||||||
|
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||||
|
// SPDX-License-Identifier: LicenseRef-SEL
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
GrantType::LiveTracing | GrantType::LiveMetrics => {
|
||||||
|
self.handle_telemetry_api_request(
|
||||||
&req,
|
&req,
|
||||||
vec!["", live_path, "live"],
|
path,
|
||||||
&AccessToken::from_id(token_info.account_id)
|
&AccessToken::from_id(token_info.account_id)
|
||||||
.with_permission(Permission::MetricsLive)
|
.with_permission(Permission::MetricsLive)
|
||||||
.with_permission(Permission::TracingLive),
|
.with_permission(Permission::TracingLive),
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// SPDX-SnippetEnd
|
// SPDX-SnippetEnd
|
||||||
|
GrantType::Troubleshoot => {
|
||||||
|
self.handle_troubleshoot_api_request(
|
||||||
|
&req,
|
||||||
|
path,
|
||||||
|
&AccessToken::from_id(token_info.account_id)
|
||||||
|
.with_permission(Permission::Troubleshoot),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(err);
|
return Err(err);
|
||||||
|
|
|
@ -353,7 +353,6 @@ impl TelemetryApi for Server {
|
||||||
access_token.assert_has_permission(Permission::TracingLive)?;
|
access_token.assert_has_permission(Permission::TracingLive)?;
|
||||||
|
|
||||||
// Issue a live telemetry token valid for 60 seconds
|
// Issue a live telemetry token valid for 60 seconds
|
||||||
|
|
||||||
Ok(JsonResponse::new(json!({
|
Ok(JsonResponse::new(json!({
|
||||||
"data": self.encode_access_token(GrantType::LiveTracing, account_id, "web", 60).await?,
|
"data": self.encode_access_token(GrantType::LiveTracing, account_id, "web", 60).await?,
|
||||||
}))
|
}))
|
||||||
|
@ -364,7 +363,6 @@ impl TelemetryApi for Server {
|
||||||
access_token.assert_has_permission(Permission::MetricsLive)?;
|
access_token.assert_has_permission(Permission::MetricsLive)?;
|
||||||
|
|
||||||
// Issue a live telemetry token valid for 60 seconds
|
// Issue a live telemetry token valid for 60 seconds
|
||||||
|
|
||||||
Ok(JsonResponse::new(json!({
|
Ok(JsonResponse::new(json!({
|
||||||
"data": self.encode_access_token(GrantType::LiveMetrics, account_id, "web", 60).await?,
|
"data": self.encode_access_token(GrantType::LiveMetrics, account_id, "web", 60).await?,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub mod report;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod sieve;
|
pub mod sieve;
|
||||||
pub mod stores;
|
pub mod stores;
|
||||||
|
pub mod troubleshoot;
|
||||||
|
|
||||||
use std::{borrow::Cow, str::FromStr, sync::Arc};
|
use std::{borrow::Cow, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ use settings::ManageSettings;
|
||||||
use sieve::SieveHandler;
|
use sieve::SieveHandler;
|
||||||
use store::write::now;
|
use store::write::now;
|
||||||
use stores::ManageStore;
|
use stores::ManageStore;
|
||||||
|
use troubleshoot::TroubleshootApi;
|
||||||
|
|
||||||
use crate::{auth::oauth::auth::OAuthApiHandler, email::crypto::CryptoHandler};
|
use crate::{auth::oauth::auth::OAuthApiHandler, email::crypto::CryptoHandler};
|
||||||
|
|
||||||
|
@ -155,6 +157,13 @@ impl ManagementApi for Server {
|
||||||
}
|
}
|
||||||
_ => Err(trc::ResourceEvent::NotFound.into_err()),
|
_ => Err(trc::ResourceEvent::NotFound.into_err()),
|
||||||
},
|
},
|
||||||
|
"troubleshoot" => {
|
||||||
|
// Validate the access token
|
||||||
|
access_token.assert_has_permission(Permission::Troubleshoot)?;
|
||||||
|
|
||||||
|
self.handle_troubleshoot_api_request(req, path, &access_token, body)
|
||||||
|
.await
|
||||||
|
}
|
||||||
// SPDX-SnippetBegin
|
// SPDX-SnippetBegin
|
||||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||||
// SPDX-License-Identifier: LicenseRef-SEL
|
// SPDX-License-Identifier: LicenseRef-SEL
|
||||||
|
|
1061
crates/jmap/src/api/management/troubleshoot.rs
Normal file
1061
crates/jmap/src/api/management/troubleshoot.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -360,6 +360,15 @@ impl DeliveryAttempt {
|
||||||
|
|
||||||
TlsRptOptions { record, interval }.into()
|
TlsRptOptions { record, interval }.into()
|
||||||
}
|
}
|
||||||
|
Err(mail_auth::Error::DnsRecordNotFound(_)) => {
|
||||||
|
trc::event!(
|
||||||
|
TlsRpt(TlsRptEvent::RecordNotFound),
|
||||||
|
SpanId = message.span_id,
|
||||||
|
Domain = domain.domain.clone(),
|
||||||
|
Elapsed = time.elapsed(),
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trc::event!(
|
trc::event!(
|
||||||
TlsRpt(TlsRptEvent::RecordFetchError),
|
TlsRpt(TlsRptEvent::RecordFetchError),
|
||||||
|
|
|
@ -220,7 +220,7 @@ pub enum NextHop<'x> {
|
||||||
|
|
||||||
impl NextHop<'_> {
|
impl NextHop<'_> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn hostname(&self) -> &str {
|
pub fn hostname(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
NextHop::MX(host) => {
|
NextHop::MX(host) => {
|
||||||
if let Some(host) = host.strip_suffix('.') {
|
if let Some(host) = host.strip_suffix('.') {
|
||||||
|
@ -234,7 +234,7 @@ impl NextHop<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fqdn_hostname(&self) -> Cow<'_, str> {
|
pub fn fqdn_hostname(&self) -> Cow<'_, str> {
|
||||||
match self {
|
match self {
|
||||||
NextHop::MX(host) => {
|
NextHop::MX(host) => {
|
||||||
if !host.ends_with('.') {
|
if !host.ends_with('.') {
|
||||||
|
|
|
@ -881,6 +881,7 @@ impl TlsRptEvent {
|
||||||
match self {
|
match self {
|
||||||
TlsRptEvent::RecordFetch => "Fetched TLS-RPT record",
|
TlsRptEvent::RecordFetch => "Fetched TLS-RPT record",
|
||||||
TlsRptEvent::RecordFetchError => "Error fetching TLS-RPT record",
|
TlsRptEvent::RecordFetchError => "Error fetching TLS-RPT record",
|
||||||
|
TlsRptEvent::RecordNotFound => "TLS-RPT record not found",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -888,6 +889,7 @@ impl TlsRptEvent {
|
||||||
match self {
|
match self {
|
||||||
TlsRptEvent::RecordFetch => "The TLS-RPT record has been fetched",
|
TlsRptEvent::RecordFetch => "The TLS-RPT record has been fetched",
|
||||||
TlsRptEvent::RecordFetchError => "An error occurred while fetching the TLS-RPT record",
|
TlsRptEvent::RecordFetchError => "An error occurred while fetching the TLS-RPT record",
|
||||||
|
TlsRptEvent::RecordNotFound => "No TLS-RPT records were found",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -478,7 +478,9 @@ impl EventType {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
EventType::TlsRpt(event) => match event {
|
EventType::TlsRpt(event) => match event {
|
||||||
TlsRptEvent::RecordFetch | TlsRptEvent::RecordFetchError => Level::Info,
|
TlsRptEvent::RecordFetch
|
||||||
|
| TlsRptEvent::RecordFetchError
|
||||||
|
| TlsRptEvent::RecordNotFound => Level::Info,
|
||||||
},
|
},
|
||||||
EventType::MtaSts(event) => match event {
|
EventType::MtaSts(event) => match event {
|
||||||
MtaStsEvent::PolicyFetch
|
MtaStsEvent::PolicyFetch
|
||||||
|
|
|
@ -545,6 +545,7 @@ pub enum MtaStsEvent {
|
||||||
pub enum TlsRptEvent {
|
pub enum TlsRptEvent {
|
||||||
RecordFetch,
|
RecordFetch,
|
||||||
RecordFetchError,
|
RecordFetchError,
|
||||||
|
RecordNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[event_type]
|
#[event_type]
|
||||||
|
|
|
@ -865,6 +865,7 @@ impl EventType {
|
||||||
EventType::Ai(AiEvent::ApiError) => 557,
|
EventType::Ai(AiEvent::ApiError) => 557,
|
||||||
EventType::Security(SecurityEvent::ScanBan) => 558,
|
EventType::Security(SecurityEvent::ScanBan) => 558,
|
||||||
EventType::Store(StoreEvent::AzureError) => 559,
|
EventType::Store(StoreEvent::AzureError) => 559,
|
||||||
|
EventType::TlsRpt(TlsRptEvent::RecordNotFound) => 560,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1470,6 +1471,7 @@ impl EventType {
|
||||||
557 => Some(EventType::Ai(AiEvent::ApiError)),
|
557 => Some(EventType::Ai(AiEvent::ApiError)),
|
||||||
558 => Some(EventType::Security(SecurityEvent::ScanBan)),
|
558 => Some(EventType::Security(SecurityEvent::ScanBan)),
|
||||||
559 => Some(EventType::Store(StoreEvent::AzureError)),
|
559 => Some(EventType::Store(StoreEvent::AzureError)),
|
||||||
|
560 => Some(EventType::TlsRpt(TlsRptEvent::RecordNotFound)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue