use crate::io::ConnectionInfo;
use crate::mail::{AddRecipientFailure, StartMailFailure, Transaction};
use crate::smtp::*;
#[derive(Debug)]
pub struct SmtpSession {
pub connection: ConnectionInfo,
pub extensions: ExtensionSet,
pub service_name: String,
pub peer_name: Option<String>,
pub output: Vec<DriverControl>,
pub input: Vec<u8>,
pub mode: Option<&'static str>,
pub transaction: Transaction,
}
impl Default for SmtpSession {
fn default() -> Self {
Self {
connection: Default::default(),
extensions: Default::default(),
service_name: "samotop".to_string(),
peer_name: Default::default(),
output: Default::default(),
input: Default::default(),
mode: Default::default(),
transaction: Default::default(),
}
}
}
impl SmtpSession {
pub const DATA_PARTIAL_MODE: &'static str = "DATA_PARTIAL";
pub const DATA_MODE: &'static str = "DATA";
pub fn new(connection: ConnectionInfo) -> Self {
Self {
connection,
..Default::default()
}
}
pub fn is_expecting_commands(&self) -> bool {
self.mode.is_none() || self.transaction.sink.is_none()
}
pub fn reset_helo(&mut self, peer_name: String) {
self.reset();
self.peer_name = Some(peer_name);
}
pub fn reset(&mut self) -> SayResult {
self.transaction = Transaction::default();
self.mode = None;
}
pub fn shutdown(&mut self) -> SayResult {
self.reset();
self.say(DriverControl::Shutdown)
}
pub fn pop_control(&mut self) -> Option<DriverControl> {
if self.output.is_empty() {
None
} else {
Some(self.output.remove(0))
}
}
pub fn say(&mut self, what: DriverControl) -> SayResult {
self.output.push(what);
}
pub fn say_reply(&mut self, c: SmtpReply) -> SayResult {
self.say(DriverControl::Response(c.to_string().into()))
}
pub fn say_ok(&mut self) -> SayResult {
self.say_reply(SmtpReply::OkInfo)
}
pub fn say_ok_info(&mut self, info: String) -> SayResult {
self.say_reply(SmtpReply::OkMessageInfo(info))
}
pub fn say_not_implemented(&mut self) -> SayResult {
self.say_reply(SmtpReply::CommandNotImplementedFailure)
}
pub fn say_invalid_syntax(&mut self) -> SayResult {
self.say_reply(SmtpReply::CommandSyntaxFailure)
}
pub fn say_command_sequence_fail(&mut self) -> SayResult {
self.say_reply(SmtpReply::CommandSequenceFailure)
}
pub fn say_service_ready(&mut self) -> SayResult {
self.say_reply(SmtpReply::ServiceReadyInfo(self.service_name.clone()))
}
pub fn say_helo(&mut self) -> SayResult {
self.say_reply(SmtpReply::OkHeloInfo {
local: self.service_name.clone(),
remote: self
.peer_name
.as_ref()
.unwrap_or(&self.connection.peer_addr)
.clone(),
extensions: vec![],
})
}
pub fn say_ehlo(&mut self) -> SayResult {
self.say_reply(SmtpReply::OkHeloInfo {
local: self.service_name.clone(),
remote: self
.peer_name
.as_ref()
.unwrap_or(&self.connection.peer_addr)
.clone(),
extensions: self.extensions.iter().map(String::from).collect(),
})
}
pub fn say_shutdown(&mut self, reply: SmtpReply) -> SayResult {
self.say_reply(reply);
self.shutdown()
}
pub fn say_shutdown_timeout(&mut self) -> SayResult {
warn!("Timeout expired.");
self.say_shutdown_service_err()
}
pub fn say_shutdown_service_err(&mut self) -> SayResult {
self.say_shutdown(SmtpReply::ServiceNotAvailableError(
self.service_name.clone(),
))
}
pub fn say_shutdown_processing_err(&mut self, description: String) -> SayResult {
error!("Processing error: {}", description);
self.say_shutdown(SmtpReply::ProcesingError)
}
pub fn say_shutdown_ok(&mut self) -> SayResult {
self.say_shutdown(SmtpReply::ClosingConnectionInfo(self.service_name.clone()))
}
pub fn say_mail_failed(&mut self, failure: StartMailFailure, description: String) -> SayResult {
use StartMailFailure as F;
error!("Sending mail failed: {:?}, {}", failure, description);
match failure {
F::TerminateSession => self.say_shutdown_service_err(),
F::Rejected => self.say_reply(SmtpReply::MailboxNotAvailableFailure),
F::InvalidSender => self.say_reply(SmtpReply::MailboxNameInvalidFailure),
F::InvalidParameter => self.say_reply(SmtpReply::UnknownMailParametersFailure),
F::InvalidParameterValue => self.say_reply(SmtpReply::ParametersNotAccommodatedError),
F::StorageExhaustedPermanently => self.say_reply(SmtpReply::StorageFailure),
F::StorageExhaustedTemporarily => self.say_reply(SmtpReply::StorageError),
F::FailedTemporarily => self.say_reply(SmtpReply::ProcesingError),
}
}
pub fn say_rcpt_failed(
&mut self,
failure: AddRecipientFailure,
description: String,
) -> SayResult {
use AddRecipientFailure as F;
error!("Adding RCPT failed: {:?}, {}", failure, description);
match failure {
F::TerminateSession => self.say_shutdown_service_err(),
F::Moved(path) => self.say_reply(SmtpReply::UserNotLocalFailure(format!("{}", path))),
F::RejectedPermanently => self.say_reply(SmtpReply::MailboxNotAvailableFailure),
F::RejectedTemporarily => self.say_reply(SmtpReply::MailboxNotAvailableError),
F::InvalidRecipient => self.say_reply(SmtpReply::MailboxNameInvalidFailure),
F::InvalidParameter => self.say_reply(SmtpReply::UnknownMailParametersFailure),
F::InvalidParameterValue => self.say_reply(SmtpReply::ParametersNotAccommodatedError),
F::StorageExhaustedPermanently => self.say_reply(SmtpReply::StorageFailure),
F::StorageExhaustedTemporarily => self.say_reply(SmtpReply::StorageError),
F::FailedTemporarily => self.say_reply(SmtpReply::ProcesingError),
}
}
pub fn say_ok_recipient_not_local(&mut self, path: SmtpPath) -> SayResult {
self.say_reply(SmtpReply::UserNotLocalInfo(format!("{}", path)))
}
pub fn say_mail_queue_refused(&mut self) -> SayResult {
self.say_reply(SmtpReply::MailboxNotAvailableFailure)
}
pub fn say_start_data_challenge(&mut self) -> SayResult {
self.say_reply(SmtpReply::StartMailInputChallenge);
self.mode = Some(Self::DATA_MODE);
}
pub fn say_start_tls(&mut self) -> SayResult {
self.say_service_ready();
self.say(DriverControl::StartTls);
}
pub fn say_mail_queue_failed_temporarily(&mut self) -> SayResult {
self.say_reply(SmtpReply::MailboxNotAvailableError)
}
pub fn say_mail_queued(&mut self, id: &str) -> SayResult {
let info = format!("Queued as {}", id);
self.say_ok_info(info)
}
}
type SayResult = ();
impl std::fmt::Display for SmtpSession {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(
f,
"Client {:?} using service {} with extensions {} on {}. There are {} input bytes and {} output items pending.",
self.peer_name,
self.service_name,
self.extensions
.iter()
.fold(String::new(), |s, r| s + format!("{}, ", r).as_ref()),
self.connection,
self.input.len(),
self.output.len()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
mail::Recipient,
smtp::{command::SmtpMail, SmtpPath},
};
#[test]
fn transaction_gets_reset() {
let mut sut = SmtpSession::default();
sut.transaction.id = "someid".to_owned();
sut.transaction.mail = Some(SmtpMail::Mail(SmtpPath::Null, vec![]));
sut.transaction.rcpts.push(Recipient::null());
sut.transaction.extra_headers.insert_str(0, "feeeha");
sut.reset();
assert!(sut.transaction.is_empty());
}
}