Skip to content
Snippets Groups Projects
Commit c7df59a8 authored by Robert Cutajar's avatar Robert Cutajar
Browse files

Added child process connector to samotop-delivery, docs status update,...

Added child process connector to samotop-delivery, docs status update, Prudence in server, enforcing CRLF in parser
parent 1a721095
No related branches found
No related tags found
No related merge requests found
Pipeline #340275683 failed
# samotop-delivery 0.13.0-samotop-dev
## Mail dispatch abstraction
samotop-delivery is a set of transports to deliver mail to,
notably to SMTP/LMTP, but also maildir... It is used in Samotop
as a delivery solution for incoming mail.
as a dispatch solution for incoming mail, but you can use it to send mail, too.
### Example
```rust
pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Result<T> = std::result::Result<T, Error>;
use samotop_delivery::prelude::{
Envelope, SmtpClient, Transport,
};
async fn smtp_transport_simple() -> Result<()> {
let envelope = Envelope::new(
Some("user@localhost".parse()?),
......@@ -29,14 +29,34 @@ async fn smtp_transport_simple() -> Result<()> {
// Create a client, connect and send
client.connect_and_send(envelope, message).await?;
Ok(())
}
```
## Features
- [x] Do it SMTP style:
- [x] Speak SMTP
- [x] Speak LMTP
- [x] Connect over TCP
- [x] Connect over Unix sockets
- [x] Connect to a Child process IO
- [x] TLS support on all connections
- [x] Reuse established connections
- [x] Do it locally:
- [x] Write mail to a MailDir
- [x] Write mail to lozizol journal
- [ ] Write mail to an MBox file - contributions welcome
- [x] Write mail to a single dir - fit for debug only
- [x] Popular integrations:
- [x] Send mail with sendmail
LMTP on Unix socket enables wide range of local delivery integrations, dovecot or postfix for instance. Some mail delivery programs speak LMTP, too.
## Credits
This is a fork of [async-smtp](https://github.com/async-email/async-smtp/releases/tag/v0.3.4)
This is a fork of [async-smtp](https://github.com/async-email/async-smtp/releases/tag/v0.3.4)
from the awesome [delta.chat](https://delta.chat) project.
## License - MIT OR Apache-2.0
......
......@@ -2,10 +2,6 @@
{{readme}}
## Credits
This is a fork of [async-smtp](https://github.com/async-email/async-smtp/releases/tag/v0.3.4)
## License - {{license}}
<sup>
......
//! samotop-delivery is a set of transports to deliver mail to,
//! notably to SMTP/LMTP, but also maildir... It is used in Samotop
//! as a delivery solution for incoming mail.
//!
//! ## Example
//!
//! ```rust
//! pub type Error = Box<dyn std::error::Error + Send + Sync>;
//! pub type Result<T> = std::result::Result<T, Error>;
//!
//! use samotop_delivery::prelude::{
//! Envelope, SmtpClient, Transport,
//! };
//!
//! async fn smtp_transport_simple() -> Result<()> {
//! let envelope = Envelope::new(
//! Some("user@localhost".parse()?),
//! vec!["root@localhost".parse()?],
//! "id".to_string(),
//! )?;
//! let message = "From: user@localhost\r\n\
//! Content-Type: text/plain\r\n\
//! \r\n\
//! Hello example"
//! .as_bytes();
//! let client = SmtpClient::new("127.0.0.1:2525")?;
//!
//! // Create a client, connect and send
//! client.connect_and_send(envelope, message).await?;
//!
//! Ok(())
//! }
//! ```
/*!
# Mail dispatch abstraction
samotop-delivery is a set of transports to deliver mail to,
notably to SMTP/LMTP, but also maildir... It is used in Samotop
as a dispatch solution for incoming mail, but you can use it to send mail, too.
## Example
```rust
pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Result<T> = std::result::Result<T, Error>;
use samotop_delivery::prelude::{
Envelope, SmtpClient, Transport,
};
async fn smtp_transport_simple() -> Result<()> {
let envelope = Envelope::new(
Some("user@localhost".parse()?),
vec!["root@localhost".parse()?],
"id".to_string(),
)?;
let message = "From: user@localhost\r\n\
Content-Type: text/plain\r\n\
\r\n\
Hello example"
.as_bytes();
let client = SmtpClient::new("127.0.0.1:2525")?;
// Create a client, connect and send
client.connect_and_send(envelope, message).await?;
Ok(())
}
```
# Features
- [x] Do it SMTP style:
- [x] Speak SMTP
- [x] Speak LMTP
- [x] Connect over TCP
- [x] Connect over Unix sockets
- [x] Connect to a Child process IO
- [x] TLS support on all connections
- [x] Reuse established connections
- [x] Do it locally:
- [x] Write mail to a MailDir
- [x] Write mail to lozizol journal
- [ ] Write mail to an MBox file - contributions welcome
- [x] Write mail to a single dir - fit for debug only
- [x] Popular integrations:
- [x] Send mail with sendmail
LMTP on Unix socket enables wide range of local delivery integrations, dovecot or postfix for instance. Some mail delivery programs speak LMTP, too.
# Credits
This is a fork of [async-smtp](https://github.com/async-email/async-smtp/releases/tag/v0.3.4)
from the awesome [delta.chat](https://delta.chat) project.
*/
#![deny(
missing_copy_implementations,
......
......@@ -16,6 +16,11 @@ impl<C: Connector> LmtpDispatch<C> {
};
Ok(variant)
}
/// How to reuse the client:
///
/// * 0 => unlimited resue
/// * 1 => no reuse
/// * n => limited to n
pub fn reuse(mut self, lifetimes: u16) -> Self {
self.client = match lifetimes {
0 => self
......
use crate::smtp::net::tls::TlsCapable;
use crate::smtp::net::tls::TlsProvider;
use crate::smtp::net::Connector;
use crate::smtp::net::TlsMode;
use crate::{smtp::net::ConnectionConfiguration, SyncFuture};
use async_std::io;
use async_std::io::Read;
use async_std::io::Write;
use async_std::process::*;
use async_std::task::ready;
use samotop_core::common::Pin;
use samotop_core::io::tls::MayBeTls;
use std::fmt::Display;
use std::future::Future;
use std::task::Poll;
/// Allows the SMTP client to spawn a child process and connect to it's IO
#[derive(Debug)]
pub struct ChildConnector<TLS> {
pub tls_mode: TlsMode,
pub provider: TLS,
}
impl<TLS: Default> Default for ChildConnector<TLS> {
fn default() -> Self {
Self {
tls_mode: TlsMode::StartTls,
provider: TLS::default(),
}
}
}
impl<TLS> Connector for ChildConnector<TLS>
where
TLS: TlsProvider + Sync + Send + 'static,
{
type Stream = TlsCapable;
/// This provider of connectivity takes care of running
/// the program given in address (which should be an executable command),
/// establishing a connection and enabling (or not) TLS upgrade.
fn connect<'s, 'c, 'a, C: ConnectionConfiguration + Sync>(
&'s self,
configuration: &'c C,
) -> SyncFuture<'a, io::Result<Self::Stream>>
where
's: 'a,
'c: 'a,
{
Box::pin(async move {
let to = configuration.address();
let child = Command::new(&to)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.kill_on_drop(true)
.reap_on_drop(true)
.spawn()?;
let stream = Box::new(ChildIo {
inner: Some(child),
closing: None,
});
let mut stream = match self.provider.get_tls_upgrade() {
Some(u) => TlsCapable::enabled(stream, u, to),
None => TlsCapable::plaintext(stream),
};
match self.tls_mode {
TlsMode::Tls => Pin::new(&mut stream).encrypt(),
TlsMode::StartTls => { /* ready! */ }
}
Ok(stream)
})
}
}
struct ChildIo {
inner: Option<Child>,
closing: Option<Pin<Box<dyn Future<Output = io::Result<Output>> + Sync + Send>>>,
}
impl Read for ChildIo {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<io::Result<usize>> {
if let Some(r) = self.inner.as_mut().and_then(|i| i.stdout.as_mut()) {
Pin::new(r).poll_read(cx, buf)
} else {
Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()))
}
}
}
impl Write for ChildIo {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<io::Result<usize>> {
if let Some(w) = self.inner.as_mut().and_then(|i| i.stdin.as_mut()) {
Pin::new(w).poll_write(cx, buf)
} else {
Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()))
}
}
fn poll_flush(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<io::Result<()>> {
if let Some(w) = self.inner.as_mut().and_then(|i| i.stdin.as_mut()) {
Pin::new(w).poll_flush(cx)
} else {
Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()))
}
}
fn poll_close(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<io::Result<()>> {
if let Some(w) = self.inner.as_mut().and_then(|i| i.stdin.as_mut()) {
ready!(Pin::new(w).poll_close(cx))?;
if let Some(inner) = self.inner.take() {
self.closing = Some(Box::pin(inner.output()));
}
}
if let Some(ref mut closing) = self.closing {
let output = ready!(closing.as_mut().poll(cx))?;
self.closing = None;
if !output.status.success() {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::Other,
ErrorMessage {
data: output.stderr,
},
)));
}
}
Poll::Ready(Ok(()))
}
}
#[derive(Debug)]
struct ErrorMessage {
data: Vec<u8>,
}
impl Display for ErrorMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"Child process error:\n{}",
String::from_utf8_lossy(self.data.as_slice())
))
}
}
impl std::error::Error for ErrorMessage {}
......@@ -6,7 +6,9 @@ mod unix;
#[cfg(unix)]
pub use self::unix::*;
mod child;
mod inet;
pub use self::child::*;
pub use self::inet::*;
use self::tls::DefaultTls;
......
......@@ -79,7 +79,7 @@ peg::parser! {
{? if input.eq_ignore_ascii_case(literal.as_bytes()) { Ok(()) } else { Err(literal) } }
pub rule starttls() -> (usize, StartTls)
= i("starttls") NL() p:position!() rest:$([_]*)
= i("starttls") CRLF() p:position!() rest:$([_]*)
{ (p, StartTls) }
pub rule command() -> ParseResult< SmtpCommand>
......@@ -113,56 +113,56 @@ peg::parser! {
{ParseResult::Err(ParseError::Mismatch("PEG - unrecognized command".into()))}
pub rule cmd_quit() -> SmtpCommand
= i("quit") NL()
= i("quit") CRLF()
{ SmtpCommand::Quit }
pub rule cmd_rset() -> SmtpCommand
= i("rset") NL()
= i("rset") CRLF()
{ SmtpCommand::Rset }
pub rule cmd_data() -> SmtpCommand
= i("data") NL()
= i("data") CRLF()
{ SmtpCommand::Data }
pub rule cmd_turn() -> SmtpCommand
= i("turn") NL()
= i("turn") CRLF()
{ SmtpCommand::Turn }
pub rule cmd_mail() -> SmtpCommand
= i("mail from:") p:path_reverse() s:strparam()* NL()
= i("mail from:") p:path_reverse() s:strparam()* CRLF()
{ SmtpCommand::Mail(SmtpMail::Mail(p, s)) }
pub rule cmd_send() ->SmtpCommand
= i("send from:") p:path_reverse() s:strparam()* NL()
= i("send from:") p:path_reverse() s:strparam()* CRLF()
{ SmtpCommand::Mail(SmtpMail::Send(p, s)) }
pub rule cmd_soml() -> SmtpCommand
= i("soml from:") p:path_reverse() s:strparam()* NL()
= i("soml from:") p:path_reverse() s:strparam()* CRLF()
{ SmtpCommand::Mail(SmtpMail::Soml(p, s)) }
pub rule cmd_saml() -> SmtpCommand
= i("saml from:") p:path_reverse() s:strparam()* NL()
= i("saml from:") p:path_reverse() s:strparam()* CRLF()
{ SmtpCommand::Mail(SmtpMail::Saml(p, s)) }
pub rule cmd_rcpt() -> SmtpCommand
= i("rcpt to:") p:path_forward() s:strparam()* NL()
= i("rcpt to:") p:path_forward() s:strparam()* CRLF()
{ SmtpCommand::Rcpt(SmtpRcpt(p, s)) }
pub rule cmd_helo() -> SmtpCommand
= verb:$(i("helo") / i("ehlo") / i("lhlo")) _ host:host() NL()
= verb:$(i("helo") / i("ehlo") / i("lhlo")) _ host:host() CRLF()
{ SmtpCommand::Helo(SmtpHelo{verb:String::from_utf8_lossy(verb).to_uppercase(), host}) }
pub rule cmd_vrfy() -> SmtpCommand
= i("vrfy") s:strparam() NL()
= i("vrfy") s:strparam() CRLF()
{ SmtpCommand::Vrfy(s) }
pub rule cmd_expn() -> SmtpCommand
= i("expn") s:strparam() NL()
= i("expn") s:strparam() CRLF()
{ SmtpCommand::Expn(s) }
pub rule cmd_noop() -> SmtpCommand
= i("noop") s:strparam()* NL()
= i("noop") s:strparam()* CRLF()
{ SmtpCommand::Noop(s) }
pub rule cmd_help() -> SmtpCommand
= i("help") s:strparam()* NL()
= i("help") s:strparam()* CRLF()
{ SmtpCommand::Help(s) }
pub rule path_forward() -> SmtpPath
......@@ -280,7 +280,7 @@ peg::parser! {
= b:$(".")
{debug_assert!(b.len()==1); b[0]}
rule NL() = quiet!{"\r\n" / "\n"} / expected!("{NL}")
rule CRLF() = quiet!{"\r\n"} / expected!("{CRLF}")
rule _() = quiet!{" "} / expected!("{SP}")
rule __() = quiet!{_ / "\t"} / expected!("{WS}")
}
......
......@@ -119,7 +119,7 @@ use rustls::ServerConfig;
use samotop::io::tls::RustlsProvider;
use samotop::mail::{Builder, Dir, Esmtp, EsmtpStartTls, Name};
use samotop::server::TcpServer;
use samotop::smtp::{Impatience, SmtpParser};
use samotop::smtp::{Impatience, Prudence, SmtpParser};
use std::path::{Path, PathBuf};
use std::time::Duration;
use structopt::StructOpt;
......@@ -141,7 +141,10 @@ async fn main_fut() -> Result<()> {
.using(Dir::new(setup.get_mail_dir())?)
.using(samotop::mail::spf::provide_viaspf())
.using(Esmtp.with(SmtpParser))
.using(Impatience::timeout(Duration::from_secs(30)));
.using(Impatience::timeout(Duration::from_secs(30)))
.using(Prudence {
wait_for_banner_delay: Some(Duration::from_millis(1234)),
});
if let Some(cfg) = setup.get_tls_config().await? {
builder = builder.using(EsmtpStartTls::with(
......
......@@ -43,17 +43,17 @@ The [samotop crate is published on crates.io](https://crates.io/crates/samotop).
- [x] MDA: Smart mailbox - multiple mailbox addresses by convention
- [x] Integration: LMTP socket - can deliver to LDA over unix or network sockets using LMTP
- [x] Integration: LMTP child process - can deliver to LDA using LMTP protocol over io with a child process
- [ ] Connector is missing and must be provided
- [x] LDA: Can process LMTP session (LHLO + delivery status per rcpt)
- [x] Antispam: SPF through `viaspf`
- [x] Anti-abuse: Command timeout
- [x] Extensibility: Modular and composable service
- [x] Antispam: Reject mails failing SPF checks - through `viaspf` crate, now async
- [x] Antispam: Strict SMTP - require CRLF
- [x] Antispam: Strict SMTP - reject session if client sends mail before banner - `Prudence`
- [x] Anti-abuse: Command timeout - `Impatience`
- [x] Extensibility: Modular and composable service - `Builder` + `Configuration` + `MailSetup` => `Service`
### To do
- [ ] Accounts: Self service account subscription through SMTP/IMAP
- [ ] MTA: Queue and queue manager, relay mail to another MTA
- [ ] Antispam: Strict SMTP (require CRLF, reject if client sends mail before banner or EHLO response)
- [ ] Antispam: whitelist and blacklist
- [ ] Antispam: greylisting
- [ ] Antispam: white/black/grey list with UI - user decides new contact handling
......@@ -81,11 +81,9 @@ Note that the API is still unstable. Please use the latest release.
There are a few interesting provisions one could take away from Samotop:
* The TCP server (`TcpServer`) - it takes IP:port's to listen `on()` and you can then `serve()` your own implementation of a `IoService`.
* The Unix socket server (`UnixServer`) - it takes socket file path to listen `on()` and you can then `serve()` the same as with the `TcpServer`.
* The SMTP service (`SmtpService`) - it takes an async IO and provides an SMTP service defined by `MailService`.
* The low level `SmtpCodec` - it translates between IO and a `Stram` of `SmtpSessionCommand`s and accepts `CodecControl`s.
* The SMTP session parser (`SmtpParser`) - it takes `&[u8]` and returns parsed commands or session.
* The SMTP session and domain model (in `samotop-core`) - these describe the domain and behavior.
* The mail delivery abstraction in `samotop-delivery` includes an SMTP/LMTP client over TCP/Unix socket, simple maildir, eventually also child process integration.
* The SMTP session parser (`SmtpParser`) - it takes `&[u8]` and returns parsed commands or data.
* The SMTP session and domain model (in `samotop-core`) - these describe the domain and behavior per RFC.
* The mail delivery abstraction in `samotop-delivery` includes an SMTP/LMTP client over TCP/Unix socket, chid process, simple maildir, sendmail.
* Extensible design - you can plug in or compose your own solution.
### SMTP Server (with STARTTLS)
......@@ -174,7 +172,7 @@ In Rust world I have so far found mostly SMTP clients.
* same: enables writing SMTP servers in Rust.
* same: includes SMTP parsing, responding and an SMTP state machine.
* different: Samotop uses PEG, Mailin uses Nom to define the SMTP parser.
* different: Samotop is async while Mailin runs on bare std blocking IO. Async introduces more dependencies, but allows us to shift to the new IO paradigm. In Samotop, the SMTP session is handled as a stream of commands and responses. Mailin uses a threadpool to schedule work, Samotop can run on a single thread thanks to async.
* different: Samotop is async while Mailin runs on bare std blocking IO. Async introduces more dependencies, but allows us to shift to the new IO paradigm. In Samotop, the SMTP session handling is a tree of futures. Mailin uses a threadpool to schedule work, Samotop can run on a single thread thanks to async.
* not too different: samotop includes a default TCP server and enables the user to implement it differently, mailin expects the user to provide a socket but a TCP server is available in mailin-embedded. Thanks to this, Mailin alone has much smaller dependency footprint. Samotop may follow suit to split the crates.
* ...
* [smtpbis](https://crates.io/crates/smtpbis) and [rustyknife](https://crates.io/crates/rustyknife) by **Jonathan Bastien-Filiatrault** are SMTP libraries on async and tokio.
......@@ -185,7 +183,7 @@ In Rust world I have so far found mostly SMTP clients.
* [rust-smtp](https://github.com/synlestidae/rust-smtp) fork of the above with progress by **synlestidae** in 2016
### Other
* [async-smtp](https://github.com/async-email/async-smtp) is an SMTP client. I've forked it as samotop-delivery.
* [async-smtp](https://github.com/async-email/async-smtp) is an SMTP client from the awesome [delta.chat](https://delta.chat) project. I've forked it as samotop-delivery.
* [lettre](https://github.com/lettre/lettre) is an SMTP client, it seems to be alive and well!
* [segimap](https://github.com/uiri/SEGIMAP) by **uiri**, that's actually an IMAP server.
* [ferric-mail](https://github.com/wraithan/ferric-mail) by **wraithan**, looks abandoned since 2014.
......
/*!
Example of delivering to LMTP over a child process IO.
Maps recipients to local users per domain.
## Testing
```
nc -C localhost 2525 <<EOF
lhlo mememe
mail from:<from@wow.example.com>
rcpt to:<to@mikesh.example.com>
rcpt to:<tree@mikesh.example.com>
rcpt to:<flour@mikesh.example.com>
data
From: Moohoo <moo@hoo.com>
To: Yeeehaw <ye@haw.com>
Subject: Try me
xoxo
.
EOF
```
*/
use async_std::task;
use regex::Regex;
use samotop::{
io::client::tls::NoTls,
mail::{Builder, Lmtp, LmtpDispatch, Mapper},
server::TcpServer,
smtp::SmtpParser,
};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
fn main() -> Result<()> {
env_logger::init();
task::block_on(main_fut())
}
async fn main_fut() -> Result<()> {
let rcpt_map = Mapper::new(vec![
(Regex::new(".*@(.*)")?, "$1@localhost".to_owned()), // use domain as a user name (all domain basket) anyone@example.org => example.org@localhost
(Regex::new("[^@a-zA-Z0-9]+")?, "-".to_owned()), // sanitize the user name example.org@localhost => example-org@localhost
]);
use samotop::io::client::ChildConnector;
let lmtp_connector: ChildConnector<NoTls> = ChildConnector::default();
let mail_service = Builder::default()
.using(
LmtpDispatch::new(
"samotop/examples/to-lmtp-child.sh".to_owned(),
lmtp_connector,
)?
.reuse(0),
)
.using(rcpt_map)
.using(Lmtp.with(SmtpParser))
.build();
TcpServer::on("localhost:2525").serve(mail_service).await
}
#!/bin/sh
cargo run --example on-cmd
\ No newline at end of file
......@@ -39,17 +39,17 @@ The [samotop crate is published on crates.io](https://crates.io/crates/samotop).
- [x] MDA: Smart mailbox - multiple mailbox addresses by convention
- [x] Integration: LMTP socket - can deliver to LDA over unix or network sockets using LMTP
- [x] Integration: LMTP child process - can deliver to LDA using LMTP protocol over io with a child process
- [ ] Connector is missing and must be provided
- [x] LDA: Can process LMTP session (LHLO + delivery status per rcpt)
- [x] Antispam: SPF through `viaspf`
- [x] Anti-abuse: Command timeout
- [x] Extensibility: Modular and composable service
- [x] Antispam: Reject mails failing SPF checks - through `viaspf` crate, now async
- [x] Antispam: Strict SMTP - require CRLF
- [x] Antispam: Strict SMTP - reject session if client sends mail before banner - `Prudence`
- [x] Anti-abuse: Command timeout - `Impatience`
- [x] Extensibility: Modular and composable service - `Builder` + `Configuration` + `MailSetup` => `Service`
## To do
- [ ] Accounts: Self service account subscription through SMTP/IMAP
- [ ] MTA: Queue and queue manager, relay mail to another MTA
- [ ] Antispam: Strict SMTP (require CRLF, reject if client sends mail before banner or EHLO response)
- [ ] Antispam: whitelist and blacklist
- [ ] Antispam: greylisting
- [ ] Antispam: white/black/grey list with UI - user decides new contact handling
......@@ -77,11 +77,9 @@ Note that the API is still unstable. Please use the latest release.
There are a few interesting provisions one could take away from Samotop:
* The TCP server (`TcpServer`) - it takes IP:port's to listen `on()` and you can then `serve()` your own implementation of a `IoService`.
* The Unix socket server (`UnixServer`) - it takes socket file path to listen `on()` and you can then `serve()` the same as with the `TcpServer`.
* The SMTP service (`SmtpService`) - it takes an async IO and provides an SMTP service defined by `MailService`.
* The low level `SmtpCodec` - it translates between IO and a `Stram` of `SmtpSessionCommand`s and accepts `CodecControl`s.
* The SMTP session parser (`SmtpParser`) - it takes `&[u8]` and returns parsed commands or session.
* The SMTP session and domain model (in `samotop-core`) - these describe the domain and behavior.
* The mail delivery abstraction in `samotop-delivery` includes an SMTP/LMTP client over TCP/Unix socket, simple maildir, eventually also child process integration.
* The SMTP session parser (`SmtpParser`) - it takes `&[u8]` and returns parsed commands or data.
* The SMTP session and domain model (in `samotop-core`) - these describe the domain and behavior per RFC.
* The mail delivery abstraction in `samotop-delivery` includes an SMTP/LMTP client over TCP/Unix socket, chid process, simple maildir, sendmail.
* Extensible design - you can plug in or compose your own solution.
## SMTP Server (with STARTTLS)
......@@ -170,7 +168,7 @@ In Rust world I have so far found mostly SMTP clients.
* same: enables writing SMTP servers in Rust.
* same: includes SMTP parsing, responding and an SMTP state machine.
* different: Samotop uses PEG, Mailin uses Nom to define the SMTP parser.
* different: Samotop is async while Mailin runs on bare std blocking IO. Async introduces more dependencies, but allows us to shift to the new IO paradigm. In Samotop, the SMTP session is handled as a stream of commands and responses. Mailin uses a threadpool to schedule work, Samotop can run on a single thread thanks to async.
* different: Samotop is async while Mailin runs on bare std blocking IO. Async introduces more dependencies, but allows us to shift to the new IO paradigm. In Samotop, the SMTP session handling is a tree of futures. Mailin uses a threadpool to schedule work, Samotop can run on a single thread thanks to async.
* not too different: samotop includes a default TCP server and enables the user to implement it differently, mailin expects the user to provide a socket but a TCP server is available in mailin-embedded. Thanks to this, Mailin alone has much smaller dependency footprint. Samotop may follow suit to split the crates.
* ...
* [smtpbis](https://crates.io/crates/smtpbis) and [rustyknife](https://crates.io/crates/rustyknife) by **Jonathan Bastien-Filiatrault** are SMTP libraries on async and tokio.
......@@ -181,7 +179,7 @@ In Rust world I have so far found mostly SMTP clients.
* [rust-smtp](https://github.com/synlestidae/rust-smtp) fork of the above with progress by **synlestidae** in 2016
## Other
* [async-smtp](https://github.com/async-email/async-smtp) is an SMTP client. I've forked it as samotop-delivery.
* [async-smtp](https://github.com/async-email/async-smtp) is an SMTP client from the awesome [delta.chat](https://delta.chat) project. I've forked it as samotop-delivery.
* [lettre](https://github.com/lettre/lettre) is an SMTP client, it seems to be alive and well!
* [segimap](https://github.com/uiri/SEGIMAP) by **uiri**, that's actually an IMAP server.
* [ferric-mail](https://github.com/wraithan/ferric-mail) by **wraithan**, looks abandoned since 2014.
......
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