1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! SMTP session audit events.
//!
//! [`AuditSink`] is an application-supplied observer that receives a
//! structured event for each meaningful milestone in the SMTP session.
//! Events carry machine-readable fields (reply codes, mechanism names)
//! but never include credentials or message body content.
//!
//! ## Attaching a sink
//!
//! Pass a boxed sink to [`SmtpClientOptions`][crate::SmtpClientOptions]:
//!
//! ```rust
//! use wasm_smtp::audit::{AuditSink, SmtpAuditEvent};
//! use wasm_smtp::SmtpClientOptions;
//! use std::sync::{Arc, Mutex};
//!
//! #[derive(Default, Clone)]
//! struct CounterSink {
//! messages_accepted: Arc<Mutex<u32>>,
//! }
//!
//! impl AuditSink for CounterSink {
//! fn on_event(&self, event: &SmtpAuditEvent<'_>) {
//! if let SmtpAuditEvent::MessageAccepted { .. } = event {
//! *self.messages_accepted.lock().unwrap() += 1;
//! }
//! }
//! }
//!
//! let sink = CounterSink::default();
//! let opts = SmtpClientOptions::new()
//! .with_audit(Box::new(sink));
//! ```
//!
//! ## Event sequence — successful authenticated send
//!
//! ```text
//! Connected
//! GreetingReceived { code: 220 }
//! EhloCompleted
//! AuthCompleted { mechanism: "AUTH SCRAM-SHA-256" }
//! MailFromAccepted { code: 250 }
//! RecipientAccepted { code: 250 } (once per recipient)
//! MessageAccepted { code: 250 }
//! QuitCompleted
//! ```
//!
//! For STARTTLS connections, `TlsUpgraded` appears between
//! `EhloCompleted` (plaintext) and the second `EhloCompleted` (post-TLS).
//!
//! ## Security
//!
//! No event carries credentials, message body content, or server reply text.
//! `MessageAccepted` carries only the reply code (not the queue ID or any
//! server-supplied message). Implementations of `AuditSink` are
//! caller-supplied; the caller is responsible for the security of the sink.
/// Observer called on each SMTP session milestone.
///
/// The method is synchronous. If you need async processing (writing to a
/// database, pushing to a metrics endpoint), spawn a task from inside
/// `on_event` and send the event over a channel.
///
/// `AuditSink` is object-safe; you can box it as `Box<dyn AuditSink>`.
/// A named session milestone emitted by the SMTP state machine.
///
/// The enum is `#[non_exhaustive]` so that future milestones can be added
/// without a breaking change. Sinks should include a `_ => {}` arm in
/// their match.
/// [`AuditSink`] that does nothing. Used when no sink is configured.
///
/// Zero overhead: the compiler can eliminate all calls to `on_event`
/// for this type.
;
/// [`AuditSink`] that collects all events into a `Vec`.
///
/// Useful in tests to verify the exact event sequence emitted by a session.
///
/// ```rust
/// use wasm_smtp::audit::{VecAuditSink, SmtpAuditEvent};
/// use wasm_smtp::SmtpClientOptions;
/// use std::sync::{Arc, Mutex};
///
/// let sink = Arc::new(VecAuditSink::default());
/// let opts = SmtpClientOptions::new()
/// .with_audit(Box::new(Arc::clone(&sink)));
///
/// // ... run the session ...
///
/// // After the session:
/// let events = sink.events();
/// assert!(matches!(events[0], SmtpAuditEvent::Connected));
/// ```
use Arc;