Skip to content
Snippets Groups Projects
Commit 615a47d0 authored by Jo Cutajar's avatar Jo Cutajar :four_leaf_clover:
Browse files

Explicit prudence for badly behaved clients

parent 4f78576a
No related branches found
No related tags found
1 merge request!36Explicit prudence for badly behaved clients
Showing
with 271 additions and 445 deletions
......@@ -4,4 +4,5 @@
target/
.cargo/
/*.log
tmp/
\ No newline at end of file
tmp/
*.pending-snap
\ No newline at end of file
......@@ -13,6 +13,7 @@ debug-build:
stage: build
only:
- branches
interruptible: true
cache:
key: &cache-key
files:
......@@ -42,6 +43,7 @@ release-build:
- /^release\/*/
- /^hotfix\/*/
- master
interruptible: true
cache:
key: *cache-key
paths: *cache-paths
......@@ -61,6 +63,7 @@ stable-test:
stage: build
only:
- branches
interruptible: true
cache:
key: *cache-key
paths: *cache-paths
......@@ -90,6 +93,7 @@ audit-test:
nightly-test:
stage: build
allow_failure: true
interruptible: true
variables:
RUST_BACKTRACE: 1
only:
......@@ -115,6 +119,7 @@ nightly-test:
stage: build
only:
- branches
interruptible: true
cache:
key: *cache-key
paths: *cache-paths
......@@ -122,10 +127,10 @@ nightly-test:
before_script:
- rustc --version --verbose
- cargo --version --verbose
- cargo build --example ${EXAMPLE:?} --color always
variables:
INPUT: samotop/examples/mailsession.txt
script:
- cargo build --example ${EXAMPLE:?} --color always
- RUST_LOG=trace cargo run --example ${EXAMPLE:?} &
- sleep 5
- nc localhost 2525 <"$INPUT" >client.log
......@@ -153,6 +158,18 @@ example-dir:
EXAMPLE: to-dirmail
OUTPUT: samotop/examples/mailbody.txt
example-prudence:
extends: .example-template
variables:
EXAMPLE: prudent
script:
- RUST_LOG=trace cargo run --example ${EXAMPLE:?} &
- sleep 5
- echo ehlo booo | nc -C localhost 2525 | tee bad-client.log
- nc -w 10 -C 127.0.0.1 2525 | tee good-client.log
- diff bad-client.log samotop/examples/prudence-bad.txt
- diff good-client.log samotop/examples/prudence-good.txt
example-cmd:
extends: .example-template
variables:
......@@ -160,7 +177,6 @@ example-cmd:
INPUT: samotop/examples/mailsessionlmtp.txt
OUTPUT: samotop/examples/mailbody.txt
script:
- cargo build --example ${EXAMPLE:?}
- cargo run --example ${EXAMPLE:?} <"$INPUT" >client.log
- cat "$INPUT"
- cat client.log
......@@ -198,6 +214,7 @@ server-version:
- echo "SAMOTOP_VERSION=$SAMOTOP_VERSION" > vars.env
- echo "SAMOTOP_IMAGE=$CI_REGISTRY_IMAGE/samotop:$SAMOTOP_VERSION" >> vars.env
- cat vars.env
interruptible: true
cache:
key: *cache-key
paths: *cache-paths
......@@ -209,6 +226,7 @@ server-version:
server-docker-image:
extends: .kaniko-build-template
stage: release
interruptible: true
variables:
COMPONENT: samotop
CONTEXT: .
......
......@@ -279,27 +279,6 @@ dependencies = [
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
"byte-tools",
]
[[package]]
name = "blocking"
version = "1.0.2"
......@@ -332,18 +311,6 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.0.1"
......@@ -358,18 +325,18 @@ checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
[[package]]
name = "cast"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57cdfa5d50aad6cb4d44dcab6101a7f79925bd59d82ca42f38a9856a28865374"
checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a"
dependencies = [
"rustc_version",
]
[[package]]
name = "cc"
version = "1.0.68"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
[[package]]
name = "cfg-if"
......@@ -548,15 +515,6 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array",
]
[[package]]
name = "dtoa"
version = "0.4.8"
......@@ -670,12 +628,6 @@ version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fast_chemail"
version = "0.9.6"
......@@ -687,9 +639,9 @@ dependencies = [
[[package]]
name = "fastrand"
version = "1.4.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77b705829d1e87f762c2df6da140b26af5839e1033aa84aa5f56bb688e4e1bdb"
checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e"
dependencies = [
"instant",
]
......@@ -781,15 +733,6 @@ dependencies = [
"slab",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "getrandom"
version = "0.2.3"
......@@ -886,8 +829,6 @@ checksum = "c4a1b21a2971cea49ca4613c0e9fe8225ecaf5de64090fddc6002284726e9244"
dependencies = [
"console",
"lazy_static",
"pest",
"pest_derive",
"serde",
"serde_json",
"serde_yaml",
......@@ -897,9 +838,9 @@ dependencies = [
[[package]]
name = "instant"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
dependencies = [
"cfg-if",
]
......@@ -985,9 +926,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.97"
version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
name = "linked-hash-map"
......@@ -1037,12 +978,6 @@ dependencies = [
"linked-hash-map",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "match_cfg"
version = "0.1.0"
......@@ -1132,12 +1067,6 @@ version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "openssl"
version = "0.10.35"
......@@ -1245,49 +1174,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
dependencies = [
"maplit",
"pest",
"sha-1",
]
[[package]]
name = "pin-project"
version = "1.0.7"
......@@ -1571,9 +1457,9 @@ dependencies = [
[[package]]
name = "rustc_version"
version = "0.3.3"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
......@@ -1643,6 +1529,7 @@ version = "0.13.0-samotop-dev"
dependencies = [
"async-std",
"futures-util",
"insta",
"log",
]
......@@ -1801,21 +1688,9 @@ dependencies = [
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.10.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
checksum = "5f3aac57ee7f3272d8395c6e4f502f434f0e289fcd62876f70daa008c20dcabe"
[[package]]
name = "serde"
......@@ -1870,18 +1745,6 @@ dependencies = [
"yaml-rust",
]
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer",
"digest",
"fake-simd",
"opaque-debug",
]
[[package]]
name = "signal-hook"
version = "0.3.9"
......@@ -1960,9 +1823,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.21"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
checksum = "69b041cdcb67226aca307e6e7be44c8806423d83e018bd662360a93dabce4d71"
dependencies = [
"clap",
"lazy_static",
......@@ -1971,9 +1834,9 @@ dependencies = [
[[package]]
name = "structopt-derive"
version = "0.4.14"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
checksum = "7813934aecf5f51a54775e00068c237de98489463968231a51746bbbc03f9c10"
dependencies = [
"heck",
"proc-macro-error",
......@@ -2043,18 +1906,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.25"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.25"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
dependencies = [
"proc-macro2",
"quote",
......@@ -2129,18 +1992,6 @@ dependencies = [
"trust-dns-proto",
]
[[package]]
name = "typenum"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-bidi"
version = "0.3.5"
......
......@@ -13,13 +13,16 @@ edition = "2018"
categories = ["email", "asynchronous", "network-programming"]
[badges]
gitlab = { repository="BrightOpen/Samotop", branch="develop" }
maintenance = { status="actively-developed" }
gitlab = { repository = "BrightOpen/Samotop", branch = "develop" }
maintenance = { status = "actively-developed" }
[features]
server = ["futures-util"]
[dependencies]
futures-util = { version="0.3", default-features=false, optional=true }
async-std = { version="1.9", default-features=false }
futures-util = { version = "0.3", default-features = false, optional = true }
async-std = { version = "1.9", default-features = false }
log = "0.4"
[dev-dependencies]
insta = { version = "1.7" }
......@@ -3,10 +3,7 @@ use crate::io::tls::MayBeTls;
use crate::io::ConnectionInfo;
use crate::io::IoService;
/// Logs an incomming connection on info level and that's it.
#[doc = "Dummy TCP service for testing samotop server"]
#[derive(Clone, Debug)]
pub struct DummyService;
pub use crate::common::Dummy as DummyService;
impl IoService for DummyService {
fn handle(
......
mod connection;
pub mod dummy;
mod dummy;
mod service;
pub mod smtp;
pub mod tls;
pub use self::connection::*;
pub use self::dummy::*;
pub use self::service::*;
use crate::{
common::*,
io::{
tls::{Io, MayBeTls, TlsCapable},
ConnectionInfo, IoService,
},
mail::{Esmtp, MailService, SessionInfo},
smtp::*,
};
/// `SmtpService` provides a stateful SMTP service on a TCP, Unix or other asyn IO conection.
///
/// It uses the given `MailService` which takes care of mail events:
/// * session setup - upon a new connection
/// * opening new mail transaction - MAIL FROM:<x@y.z>
/// * adding recipients - RCPT FROM:<a@b.c>
/// * streaming data - DATA...
///
/// It uses the given `Parser` to extract SMTP commands from the input strings.
/// This is essential because the commands drive the session. All commands
/// are `apply()`d to the `SmtpState`.
///
/// Internally it uses the `SmtpDriver` responsible for parsing commands and applying `Action`s
/// and serializing `DriverControl` items.
///
/// It is effectively a composition and setup of components required to serve SMTP.
///
#[derive(Clone)]
pub struct SmtpService<S> {
mail_service: Arc<S>,
}
impl<S> SmtpService<S>
where
S: MailService + Send + Sync + 'static,
{
pub fn new(mail_service: S) -> Self {
Self {
mail_service: Arc::new(mail_service),
}
}
}
impl<S> IoService for SmtpService<S>
where
S: MailService + Send + Sync + 'static,
{
fn handle(
&self,
io: Result<Box<dyn MayBeTls>>,
connection: ConnectionInfo,
) -> S1Fut<'static, Result<()>> {
let mail_service = self.mail_service.clone();
Box::pin(async move {
info!("New peer connection {}", connection);
let mut io = io?;
let mut sess = SessionInfo::new(connection, "".to_owned());
// Add tls if needed and available
if !io.can_encrypt() && !io.is_encrypted() {
if let Some(upgrade) = mail_service.get_tls_upgrade() {
let plain: Box<dyn Io> = Box::new(io);
io = Box::new(TlsCapable::enabled(plain, upgrade, String::default()));
}
}
// enable STARTTLS extension if it can be used
if io.can_encrypt() && !io.is_encrypted() {
sess.extensions.enable(&extension::STARTTLS);
}
let mut driver = SmtpDriver::new(io);
let interpretter = mail_service.get_interpretter();
let mut state = SmtpState::new(mail_service);
// send connection info
Esmtp.apply(sess, &mut state).await;
while driver.is_open() {
// fetch and apply commands
driver.drive(&interpretter, &mut state).await?
}
Ok(())
})
}
}
......@@ -2,6 +2,57 @@ mod notls;
mod stream;
mod traits;
use core::panic;
pub use notls::*;
pub use stream::*;
pub use traits::*;
use crate::common::*;
impl Read for Dummy {
fn poll_read(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &mut [u8],
) -> Poll<io::Result<usize>> {
panic!("Cannot read on dummy IO")
}
}
impl Write for Dummy {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &[u8],
) -> Poll<io::Result<usize>> {
panic!("Cannot write on dummy IO")
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
panic!("Cannot flush on dummy IO")
}
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
// tolerate close
Poll::Ready(Ok(()))
}
}
impl MayBeTls for Dummy {
fn enable_encryption(&mut self, _upgrade: Box<dyn self::TlsUpgrade>, _name: String) {
panic!("Cannot enable encryption on dummy IO")
}
fn encrypt(self: std::pin::Pin<&mut Self>) {
panic!("Cannot encryptn on dummy IO")
}
fn can_encrypt(&self) -> bool {
false
}
fn is_encrypted(&self) -> bool {
false
}
}
use super::{TlsProvider, TlsUpgrade};
use crate::common::*;
use crate::io::tls::Io;
use crate::{
common::*,
mail::{Configuration, MailSetup},
};
#[derive(Default, Debug, Clone, Copy)]
pub struct NoTls;
......@@ -53,9 +50,3 @@ impl Write for Impossible {
unreachable!()
}
}
impl MailSetup for NoTls {
fn setup(self, config: &mut Configuration) {
config.tls = Box::new(self);
}
}
use super::{Io, MayBeTls, TlsUpgrade};
use crate::common::*;
use core::panic;
use std::fmt;
pub struct TlsCapable {
......@@ -56,6 +57,15 @@ impl MayBeTls for TlsCapable {
State::Failed => false,
}
}
fn enable_encryption(&mut self, upgrade: Box<dyn super::TlsUpgrade>, name: String) {
self.state = match std::mem::replace(&mut self.state, State::Failed) {
State::Enabled(io, _, _) => State::Enabled(io, upgrade, name),
State::Done(io, _) => State::Enabled(io, upgrade, name),
State::Handshake(_) => panic!("currently upgrading"),
State::Failed => panic!("IO failed"),
}
}
}
impl TlsCapable {
pub fn plaintext(io: Box<dyn Io>) -> Self {
......
......@@ -4,6 +4,7 @@ use std::ops::DerefMut;
/// A stream implementing this trait may be able to upgrade to TLS
/// But maybe not...
pub trait MayBeTls: Unpin + Read + Write + Sync + Send {
fn enable_encryption(&mut self, upgrade: Box<dyn super::TlsUpgrade>, name: String);
/// Initiates the TLS negotiations.
/// The stream must then block all reads/writes until the
/// underlying TLS handshake is done.
......@@ -31,6 +32,10 @@ where
fn is_encrypted(&self) -> bool {
TLSIO::is_encrypted(T::deref(self))
}
fn enable_encryption(&mut self, upgrade: Box<dyn super::TlsUpgrade>, name: String) {
TLSIO::enable_encryption(T::deref_mut(self), upgrade, name)
}
}
pub trait Io: Read + Write + Sync + Send + Unpin {}
......
......@@ -28,6 +28,19 @@ pub mod common {
pub use std::sync::Arc;
pub use std::task::{Context, Poll};
#[derive(Debug, Copy, Clone)]
pub struct Dummy;
/// In the absence of random number generator produces a time based identifier
/// It is not reliable nor secure, RNG/PRNG should be preffered.
pub fn time_based_id() -> String {
fn nonnumber(input: char) -> bool {
!input.is_ascii_digit()
}
// for the lack of better unique string without extra dependencies
format!("{:?}", std::time::Instant::now()).replace(nonnumber, "")
}
/// Enable async close
/// TODO: remove after https://github.com/async-rs/async-std/issues/977
pub trait WriteClose {
......@@ -63,6 +76,8 @@ pub mod common {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// CHECKME: sometimes, last response is not written (I guess, based on NetCat experience)
//ready!(Pin::new(&mut *self.writer).poll_flush(cx))?;
Pin::new(&mut *self.writer).poll_close(cx)
}
}
......
use crate::{
common::*,
io::tls::{NoTls, TlsProvider, TlsUpgrade},
mail::{
AddRecipientRequest, AddRecipientResult, DispatchResult, EsmtpService, MailDispatch,
MailGuard, MailService, MailSetup, SessionInfo, StartMailRequest, StartMailResult,
Transaction,
},
smtp::{Dummy, Interpret},
mail::{EsmtpService, MailDispatch, MailGuard, MailSetup, Service},
smtp::Interpret,
};
use super::ParserProvider;
/// Builds MailService from components
#[derive(Default)]
pub struct Builder {
config: Configuration,
}
/// Service builder configuration
#[derive(Debug)]
pub struct Configuration {
pub id: String,
pub tls: Box<dyn TlsProvider + Sync + Send + 'static>,
pub interpretter: Box<Arc<dyn Interpret + Send + Sync>>,
/// ID used for identifying this instance in logs
pub logging_id: String,
pub interpret: Vec<Box<dyn Interpret + Send + Sync>>,
pub dispatch: Vec<Box<dyn MailDispatch + Sync + Send + 'static>>,
pub guard: Vec<Box<dyn MailGuard + Sync + Send + 'static>>,
pub esmtp: Vec<Box<dyn EsmtpService + Sync + Send + 'static>>,
}
impl Builder {
/// Use a given MailSetup to build a MailService.
///
/// See MailSetup for examples.
pub fn using(mut self, setup: impl MailSetup) -> Self {
trace!("Builder {} using setup {:?}", self.config.id, setup);
trace!(
"Service builder {} using setup {:?}",
self.config.logging_id,
setup
);
setup.setup(&mut self.config);
self
}
pub fn into_service(self) -> impl MailService {
self.config
/// Finalize and produce the MailService.
pub fn build(self) -> Service {
Service::new(self.config)
}
}
impl Default for Configuration {
fn default() -> Self {
Self {
id: Default::default(),
tls: Box::new(NoTls),
interpretter: Box::new(Arc::new(Dummy)),
logging_id: time_based_id(),
interpret: Default::default(),
dispatch: Default::default(),
guard: Default::default(),
esmtp: Default::default(),
}
}
}
impl MailDispatch for Configuration {
fn send_mail<'a, 's, 'f>(
&'a self,
session: &'s SessionInfo,
mut transaction: Transaction,
) -> S1Fut<'f, DispatchResult>
where
'a: 'f,
's: 'f,
{
debug!(
"Dispatch {} with {} dispatchers sending mail {:?} on session {:?}",
self.id,
self.dispatch.len(),
transaction,
session
);
let fut = async move {
for disp in self.dispatch.iter() {
trace!("Dispatch {} send_mail calling {:?}", self.id, disp);
transaction = disp.send_mail(session, transaction).await?;
}
Ok(transaction)
};
Box::pin(fut)
}
}
impl MailGuard for Configuration {
fn add_recipient<'a, 'f>(
&'a self,
mut request: AddRecipientRequest,
) -> S2Fut<'f, AddRecipientResult>
where
'a: 'f,
{
debug!(
"Guard {} with {} guards adding recipient {:?}",
self.id,
self.guard.len(),
request
);
let fut = async move {
for guard in self.guard.iter() {
trace!("Guard {} add_recipient calling {:?}", self.id, guard);
match guard.add_recipient(request).await {
AddRecipientResult::Inconclusive(r) => request = r,
otherwise => return otherwise,
}
}
AddRecipientResult::Inconclusive(request)
};
Box::pin(fut)
}
fn start_mail<'a, 's, 'f>(
&'a self,
session: &'s SessionInfo,
mut request: StartMailRequest,
) -> S2Fut<'f, StartMailResult>
where
'a: 'f,
's: 'f,
{
debug!(
"Guard {} with {} guards starting mail {:?}",
self.id,
self.guard.len(),
request
);
let fut = async move {
for guard in self.guard.iter() {
trace!("Guard {} start_mail calling {:?}", self.id, guard);
match guard.start_mail(session, request).await {
StartMailResult::Accepted(r) => request = r,
otherwise => return otherwise,
}
}
StartMailResult::Accepted(request)
};
Box::pin(fut)
}
}
impl EsmtpService for Configuration {
fn prepare_session(&self, session: &mut SessionInfo) {
debug!(
"Esmtp {} with {} esmtps preparing session {:?}",
self.id,
self.esmtp.len(),
session
);
for esmtp in self.esmtp.iter() {
trace!("Esmtp {} prepare_session calling {:?}", self.id, esmtp);
esmtp.prepare_session(session);
}
}
}
impl TlsProvider for Configuration {
fn get_tls_upgrade(&self) -> Option<Box<dyn TlsUpgrade>> {
self.tls.get_tls_upgrade()
}
}
impl ParserProvider for Configuration {
fn get_interpretter(&self) -> Box<dyn Interpret + Sync + Send> {
Box::new(self.interpretter.clone())
}
}
......@@ -3,7 +3,9 @@
use std::fmt;
use crate::common::*;
use crate::io::tls::MayBeTls;
use crate::mail::*;
use crate::smtp::SmtpState;
//use uuid::Uuid;
#[derive(Clone, Debug)]
......@@ -30,8 +32,18 @@ impl MailSetup for DebugMailService {
}
}
impl EsmtpService for DebugMailService {
fn prepare_session(&self, session: &mut SessionInfo) {
info!("{}: I am {}", self.id, session.service_name);
fn prepare_session<'a, 'i, 's, 'f>(
&'a self,
_io: &'i mut Box<dyn MayBeTls>,
state: &'s mut SmtpState,
) -> S1Fut<'f, ()>
where
'a: 'f,
'i: 'f,
's: 'f,
{
info!("{}: I am {}", self.id, state.session.service_name);
Box::pin(ready(()))
}
}
......
use crate::{
common::*,
io::tls::MayBeTls,
smtp::{Drive, Interpret},
};
pub trait DriverProvider: fmt::Debug {
fn get_driver<'io>(&self, io: &'io mut (dyn DriverIo)) -> Box<dyn Drive + Sync + Send + 'io>;
fn get_interpretter(&self) -> Box<dyn Interpret + Sync + Send>;
}
impl<T> DriverProvider for Arc<T>
where
T: DriverProvider,
{
fn get_driver<'io>(&self, io: &'io mut (dyn DriverIo)) -> Box<dyn Drive + Sync + Send + 'io> {
T::get_driver(self, io)
}
fn get_interpretter(&self) -> Box<dyn Interpret + Sync + Send> {
T::get_interpretter(self)
}
}
pub trait DriverIo: MayBeTls + Read + Write + Send + Sync + Unpin {}
impl<T> DriverIo for T where T: MayBeTls + Read + Write + Send + Sync + Unpin {}
use crate::common::*;
use crate::mail::SessionInfo;
use crate::io::tls::MayBeTls;
use crate::smtp::SmtpState;
/**
The service which implements this trait delivers ESMTP extensions.
```
# use samotop_core::smtp::*;
# use samotop_core::mail::*;
use samotop_core::common::S1Fut;
use samotop_core::smtp::*;
use samotop_core::mail::*;
use samotop_core::io::tls::MayBeTls;
/// This mail service canhabdle 8-bit MIME
#[derive(Clone, Debug)]
pub struct EnableEightBit<T>(T);
pub struct EnableEightBit;
impl<T> EsmtpService for EnableEightBit<T>
where
T: EsmtpService,
impl EsmtpService for EnableEightBit
{
fn prepare_session(&self, session: &mut SessionInfo) {
self.0.prepare_session(session);
session
.extensions
.enable(&extension::EIGHTBITMIME);
fn prepare_session<'a, 'i, 's, 'f>(
&'a self,
_io: &'i mut Box<dyn MayBeTls>,
state: &'s mut SmtpState,
) -> S1Fut<'f, ()>
where
'a: 'f,
'i: 'f,
's: 'f
{
Box::pin(async move {
state.session
.extensions
.enable(&extension::EIGHTBITMIME);
})
}
}
```
*/
pub trait EsmtpService: fmt::Debug {
fn prepare_session(&self, session: &mut SessionInfo);
fn prepare_session<'a, 'i, 's, 'f>(
&'a self,
io: &'i mut Box<dyn MayBeTls>,
state: &'s mut SmtpState,
) -> S1Fut<'f, ()>
where
'a: 'f,
'i: 'f,
's: 'f;
}
impl<T> EsmtpService for Arc<T>
where
T: EsmtpService,
{
fn prepare_session(&self, session: &mut SessionInfo) {
T::prepare_session(self, session)
fn prepare_session<'a, 'i, 's, 'f>(
&'a self,
io: &'i mut Box<dyn MayBeTls>,
state: &'s mut SmtpState,
) -> S1Fut<'f, ()>
where
'a: 'f,
'i: 'f,
's: 'f,
{
T::prepare_session(self, io, state)
}
}
......@@ -98,11 +98,6 @@ pub struct AddRecipientRequest {
#[allow(clippy::large_enum_variant)]
pub enum AddRecipientResult {
Inconclusive(AddRecipientRequest),
/// The whole mail transaction failed, subsequent RCPT and DATA will fail
/// 421 <domain> Service not available, closing transmission channel
/// (This may be a reply to any command if the service knows it must
/// shut down)
TerminateSession(String),
/// Failed with description that should include the ID, see `AddRecipientFailure`
Failed(Transaction, AddRecipientFailure, String),
/// 251 User not local; will forward to <forward-path>
......@@ -113,6 +108,11 @@ pub enum AddRecipientResult {
#[derive(Debug, Clone)]
pub enum AddRecipientFailure {
/// The whole mail transaction failed, subsequent RCPT and DATA will fail
/// 421 <domain> Service not available, closing transmission channel
/// (This may be a reply to any command if the service knows it must
/// shut down)
TerminateSession,
/// 550 Requested action not taken: mailbox unavailable (e.g., mailbox
/// not found, no access, or command rejected for policy reasons)
RejectedPermanently,
......
use crate::{io::tls::TlsProvider, mail::*};
use crate::{io::IoService, mail::*, smtp::Interpret};
pub trait MailService:
TlsProvider + ParserProvider + EsmtpService + MailGuard + MailDispatch
IoService + Interpret + DriverProvider + EsmtpService + MailGuard + MailDispatch
{
}
impl<T> MailService for T where
T: TlsProvider + ParserProvider + EsmtpService + MailGuard + MailDispatch
T: IoService + Interpret + DriverProvider + EsmtpService + MailGuard + MailDispatch
{
}
mod builder;
mod debug;
mod dispatch;
mod driver;
mod esmtp;
mod guard;
mod mailservice;
mod name;
mod null;
mod parser;
mod recipient;
mod rfc2033;
mod rfc3207;
mod rfc5321;
mod rfc821;
mod service;
mod session;
mod setup;
mod tls;
......@@ -20,18 +21,19 @@ mod transaction;
pub use self::builder::*;
pub use self::debug::*;
pub use self::dispatch::*;
pub use self::driver::*;
pub use self::esmtp::*;
pub use self::guard::*;
pub use self::mailservice::*;
pub use self::name::*;
pub use self::null::*;
pub use self::parser::*;
pub use self::recipient::*;
pub use self::rfc2033::*;
pub use self::rfc3207::*;
pub use self::rfc5321::*;
pub use self::rfc5321::*;
pub use self::rfc821::*;
pub use self::service::*;
pub use self::session::*;
pub use self::setup::*;
pub use self::tls::*;
......
use super::{EsmtpService, MailSetup, SessionInfo};
use super::{EsmtpService, MailSetup};
use crate::common::{ready, S1Fut};
use crate::io::tls::MayBeTls;
use crate::smtp::SmtpState;
/// MailSetup that uses the given service name for a session.
#[derive(Debug)]
pub struct Name {
name: String,
......@@ -12,11 +16,23 @@ impl Name {
}
}
impl EsmtpService for Name {
fn prepare_session(&self, session: &mut SessionInfo) {
session.service_name = self.name.clone();
/// Use a given name as a service name in the session
fn prepare_session<'a, 'i, 's, 'f>(
&'a self,
_io: &'i mut Box<dyn MayBeTls>,
state: &'s mut SmtpState,
) -> S1Fut<'f, ()>
where
'a: 'f,
'i: 'f,
's: 'f,
{
state.session.service_name = self.name.clone();
Box::pin(ready(()))
}
}
impl MailSetup for Name {
/// Add self as an ESMTP service so it can configure service name for each session
fn setup(self, config: &mut super::Configuration) {
config.esmtp.insert(0, Box::new(self))
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment