#![allow(dead_code)]
use std::io::{Read, Write};
use io_smtp::{
coroutine::*,
message::SmtpMessageSend,
rfc5321::{
ehlo::SmtpEhlo,
greeting::SmtpGreetingGet,
helo::SmtpHelo,
mail::SmtpMail,
noop::SmtpNoop,
quit::SmtpQuit,
rcpt::SmtpRcpt,
rset::SmtpRset,
types::{
domain::Domain, ehlo_domain::EhloDomain, forward_path::ForwardPath,
local_part::LocalPart, mailbox::Mailbox, reverse_path::ReversePath,
},
},
sasl::{
auth_login::{SmtpAuthLogin, SmtpAuthLoginOptions},
auth_plain::{SmtpAuthPlain, SmtpAuthPlainOptions},
},
};
use pimalaya_stream::{std::stream::StreamStd, tls::Tls};
use secrecy::SecretString;
pub enum Auth {
None,
Plain { username: String, password: String },
Login { username: String, password: String },
}
pub fn run_smtp(host: &str, auth: Auth, email: &str) {
let _ = env_logger::try_init();
let stream = StreamStd::connect_tcp(host, 25).expect("TCP connect");
run(stream, auth, email)
}
pub fn run_smtps(host: &str, port: u16, auth: Auth, email: &str) {
let _ = env_logger::try_init();
let stream = StreamStd::connect_tls(host, port, &Tls::default()).expect("TLS connect");
run(stream, auth, email)
}
fn read_chunk<S: Read>(stream: &mut S, buf: &mut [u8]) -> Vec<u8> {
let n = stream.read(buf).expect("read");
buf[..n].to_vec()
}
fn run(mut stream: impl Read + Write, auth: Auth, email: &str) {
let domain = Domain::parse(b"pimalaya.org").unwrap();
let ehlo_domain: EhloDomain<'static> = domain.clone().into();
let mut buf = [0u8; 4096];
let mut coroutine = SmtpGreetingGet::new();
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(_)) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("GREETING: {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(_)) => arg = None,
}
}
let mut coroutine = SmtpHelo::new(domain);
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(())) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("HELO: {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
let mut coroutine = SmtpEhlo::new(ehlo_domain.clone());
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(_)) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("EHLO: {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
match auth {
Auth::None => {}
Auth::Plain { username, password } => {
let password = SecretString::from(password);
let mut coroutine = SmtpAuthPlain::new(
&username,
&password,
ehlo_domain.clone(),
SmtpAuthPlainOptions::default(),
);
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(())) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("AUTH PLAIN: {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
}
Auth::Login { username, password } => {
let password = SecretString::from(password);
let mut coroutine = SmtpAuthLogin::new(
&username,
&password,
ehlo_domain.clone(),
SmtpAuthLoginOptions::default(),
);
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(())) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("AUTH LOGIN: {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
}
}
let mut coroutine = SmtpNoop::new();
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(())) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("NOOP: {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
let (local, domain_part) = email.split_once('@').unwrap();
let mailbox = Mailbox {
local_part: LocalPart(local.to_owned().into()),
domain: Domain::parse(domain_part.as_bytes()).unwrap().into(),
};
let reverse_path = ReversePath::Mailbox(mailbox.clone());
let forward_path = ForwardPath(mailbox);
let mut coroutine = SmtpMail::new(reverse_path.clone(), Vec::new());
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(())) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("MAIL FROM (aborted): {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
let mut coroutine = SmtpRcpt::new(forward_path.clone(), Vec::new());
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(())) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("RCPT TO (aborted): {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
let mut coroutine = SmtpRset::new();
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(())) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("RSET: {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
let eml = [
&format!("From: io-smtp test <{email}>"),
&format!("To: io-smtp test <{email}>"),
"Subject: io-smtp integration test",
"Date: Thu, 01 Jan 2026 00:00:00 +0000",
"MIME-Version: 1.0",
"Content-Type: text/plain; charset=utf-8",
"",
"This is an automated test email from io-smtp integration tests.",
]
.join("\r\n");
let mut coroutine = SmtpMessageSend::new(reverse_path, [forward_path], eml.into_bytes());
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(())) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("send message: {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
let mut coroutine = SmtpQuit::new();
let mut chunk: Vec<u8>;
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(())) => break,
SmtpCoroutineState::Complete(Err(err)) => panic!("QUIT: {err}"),
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
stream.write_all(&bytes).expect("write")
}
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
chunk = read_chunk(&mut stream, &mut buf);
arg = Some(&chunk);
}
}
}
}