oxedyne/fe2o3/fe2o3_net/src/smtp/handler.rs
2.8 KiB, 58 runs
created by r1870400018:597, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! Application-level hooks the SMTP server calls on accepted messages. |
| 2 | //! |
| 3 | //! A `SmtpHandler` decides what to do with a fully-received RFC 5322 |
| 4 | //! message after the server has already enforced the protocol: receive |
| 5 | //! path delivery to a local mailbox, submission path enqueue for outbound |
| 6 | //! delivery, etc. The trait is split into two methods so the same handler |
| 7 | //! type can serve both ports with different policies. |
| 8 | //! |
| 9 | //! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\ |
| 10 | //! Anthropic Claude |
| 11 | |
| 12 | use crate::mail::store::MailUser; |
| 13 | |
| 14 | use oxedyne_fe2o3_core::prelude::*; |
| 15 | |
| 16 | use std::net::SocketAddr; |
| 17 | |
| 18 | |
| 19 | /// The server fills this in from the commands received before `DATA`, then |
| 20 | /// hands it to a `SmtpHandler` with the raw message bytes. |
| 21 | #[derive(Clone, Debug)] |
| 22 | pub struct SmtpTransaction { |
| 23 | pub mail_from: String, // empty is the null reverse-path `<>`, RFC 5321 |
| 24 | pub rcpt_to: Vec<String>, // in the order received |
| 25 | pub helo_domain: String, |
| 26 | pub auth_user: Option<MailUser>, // None on the port 25 receive path |
| 27 | pub peer: SocketAddr, |
| 28 | pub tls: bool, // implicit TLS or an in-session STARTTLS upgrade |
| 29 | // Dot-unstuffed and CRLF-preserving, without the terminating `<CRLF>.<CRLF>`. |
| 30 | pub raw_message: Vec<u8>, |
| 31 | } |
| 32 | |
| 33 | /// The queue id in `Accepted` reaches the client on the `250 OK` line, so an |
| 34 | /// administrator can grep the logs by it. |
| 35 | #[derive(Clone, Debug)] |
| 36 | pub enum HandlerOutcome { |
| 37 | Accepted(String), // queue id, echoed in the `250` |
| 38 | RejectPermanent(String), // reason, returned in a `550` |
| 39 | RejectTemporary(String), // reason, returned in a `451` |
| 40 | } |
| 41 | |
| 42 | /// Implementations are expected to be cheap to clone, typically via an internal |
| 43 | /// `Arc`, so one handler serves every accept loop without contention. Both |
| 44 | /// methods are synchronous: the server calls them inside a |
| 45 | /// `tokio::task::spawn_blocking` so the underlying I/O does not block the |
| 46 | /// runtime. |
| 47 | pub trait SmtpHandler: Clone + Send + Sync + 'static { |
| 48 | /// The port 25 path: deliver into every local mailbox `rcpt_to` names. A |
| 49 | /// recipient that does not resolve locally is refused at `RCPT` time, so |
| 50 | /// by here every recipient has already been accepted. |
| 51 | fn deliver_inbound(&self, txn: SmtpTransaction) -> Outcome<HandlerOutcome>; |
| 52 | |
| 53 | /// The port 587 path: enqueue for outbound delivery, typically through |
| 54 | /// `crate::smtp::client::OutboundClient`, after DKIM signing where a key |
| 55 | /// is configured. |
| 56 | fn submit_outbound(&self, txn: SmtpTransaction) -> Outcome<HandlerOutcome>; |
| 57 | |
| 58 | /// Will this `RCPT TO` be taken? Asked on the receive path only: |
| 59 | /// submission listeners skip the check, since an authenticated client may |
| 60 | /// relay anywhere. |
| 61 | fn rcpt_acceptable(&self, address: &str) -> bool; |
| 62 | } |