1use core::fmt;
48
49use alloc::{
50 borrow::Cow,
51 string::{String, ToString},
52 vec::Vec,
53};
54
55use bounded_static::IntoBoundedStatic;
56use log::debug;
57use secrecy::SecretBox;
58use thiserror::Error;
59
60use crate::{
61 coroutine::*,
62 rfc4954::{auth::SmtpAuthCommand, auth_data::SmtpAuthData},
63 rfc5321::{
64 SmtpEhloDomain, SmtpReplyCode,
65 ehlo::{SmtpEhlo, SmtpEhloError},
66 },
67 send::*,
68 smtp_try,
69};
70
71pub const ANONYMOUS: &str = "ANONYMOUS";
73
74#[derive(Clone, Debug, Eq, PartialEq)]
76pub struct SmtpAuthAnonymousOptions {
77 pub initial_request: bool,
80 pub ensure_capabilities: bool,
83}
84
85impl Default for SmtpAuthAnonymousOptions {
86 fn default() -> Self {
87 Self {
88 initial_request: true,
89 ensure_capabilities: false,
90 }
91 }
92}
93
94#[derive(Debug, Error)]
96pub enum SmtpAuthAnonymousError {
97 #[error("SMTP AUTH ANONYMOUS failed: rejected {code} {message}")]
99 Rejected {
100 code: u16,
102 message: String,
104 },
105 #[error("SMTP AUTH ANONYMOUS failed: server sent an unexpected continuation request")]
107 UnexpectedContinuationRequest,
108 #[error("SMTP AUTH ANONYMOUS failed: server did not send the expected continuation request")]
110 ExpectedContinuationRequest,
111 #[error("SMTP AUTH ANONYMOUS failed: {0}")]
113 Send(#[from] SmtpCommandSendError),
114 #[error(transparent)]
116 Ehlo(#[from] SmtpEhloError),
117}
118
119pub struct SmtpAuthAnonymous {
121 state: State,
122 domain: Option<SmtpEhloDomain<'static>>,
123 payload: Option<Vec<u8>>,
124 opts: SmtpAuthAnonymousOptions,
125}
126
127impl SmtpAuthAnonymous {
128 pub fn new(
130 trace: Option<&str>,
131 domain: SmtpEhloDomain<'_>,
132 opts: SmtpAuthAnonymousOptions,
133 ) -> Self {
134 let payload = trace.unwrap_or("").as_bytes().to_vec();
135
136 let state = if opts.initial_request {
137 let cmd = SmtpAuthCommand {
138 mechanism: Cow::Borrowed(ANONYMOUS),
139 initial_response: Some(SecretBox::new(payload.clone().into_boxed_slice())),
140 };
141 State::Send(SmtpCommandSend::new(cmd))
142 } else {
143 let cmd = SmtpAuthCommand {
144 mechanism: Cow::Borrowed(ANONYMOUS),
145 initial_response: None,
146 };
147 State::Send(SmtpCommandSend::new(cmd))
148 };
149
150 Self {
151 state,
152 domain: Some(domain.into_static()),
153 payload: Some(payload),
154 opts,
155 }
156 }
157}
158
159impl SmtpCoroutine for SmtpAuthAnonymous {
160 type Yield = SmtpYield;
161 type Return = Result<(), SmtpAuthAnonymousError>;
162
163 fn resume(&mut self, arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
164 loop {
165 match &mut self.state {
166 State::Send(send) => {
167 let out = smtp_try!(send, arg);
168
169 if out.response.code == SmtpReplyCode::AUTH_SUCCESSFUL {
170 if self.opts.initial_request {
171 self.advance_after_auth();
172 continue;
173 }
174 return SmtpCoroutineState::Complete(Err(
175 SmtpAuthAnonymousError::ExpectedContinuationRequest,
176 ));
177 }
178
179 if out.response.code == SmtpReplyCode::AUTH_CONTINUE {
180 if self.opts.initial_request {
181 return SmtpCoroutineState::Complete(Err(
182 SmtpAuthAnonymousError::UnexpectedContinuationRequest,
183 ));
184 }
185 let payload = self.payload.take().expect("payload taken twice");
186 let data = SmtpAuthData::r#continue(payload.into_boxed_slice());
187 self.state = State::Continue(SmtpCommandSend::new(data));
188 debug!("challenge received, sending trace");
189 continue;
190 }
191
192 let code = out.response.code.code();
193 let message = out.response.text().to_string();
194 return SmtpCoroutineState::Complete(Err(SmtpAuthAnonymousError::Rejected {
195 code,
196 message,
197 }));
198 }
199 State::Continue(send) => {
200 let out = smtp_try!(send, arg);
201
202 if out.response.code == SmtpReplyCode::AUTH_SUCCESSFUL {
203 self.advance_after_auth();
204 continue;
205 }
206
207 let code = out.response.code.code();
208 let message = out.response.text().to_string();
209 return SmtpCoroutineState::Complete(Err(SmtpAuthAnonymousError::Rejected {
210 code,
211 message,
212 }));
213 }
214 State::Ehlo(ehlo) => {
215 let _ = smtp_try!(ehlo, arg);
216 debug!("capabilities refreshed");
217 return SmtpCoroutineState::Complete(Ok(()));
218 }
219 State::Done => return SmtpCoroutineState::Complete(Ok(())),
220 }
221 }
222 }
223}
224
225impl SmtpAuthAnonymous {
226 fn advance_after_auth(&mut self) {
227 let _ = self.payload.take();
228 debug!("authenticated");
229 if self.opts.ensure_capabilities {
230 let domain = self.domain.take().expect("domain taken twice");
231 self.state = State::Ehlo(SmtpEhlo::new(domain));
232 } else {
233 self.state = State::Done;
234 }
235 }
236}
237
238enum State {
239 Send(SmtpCommandSend<SmtpAuthCommand<'static>>),
240 Continue(SmtpCommandSend<SmtpAuthData>),
241 Ehlo(SmtpEhlo),
242 Done,
243}
244
245impl fmt::Display for State {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 match self {
248 Self::Send(_) => f.write_str("send auth anonymous"),
249 Self::Continue(_) => f.write_str("send trace"),
250 Self::Ehlo(_) => f.write_str("refresh capabilities"),
251 Self::Done => f.write_str("done"),
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use core::str::from_utf8;
259
260 use alloc::{borrow::Cow, vec::Vec};
261
262 use crate::{
263 coroutine::*,
264 rfc5321::{SmtpDomain, SmtpEhloDomain},
265 sasl::auth_anonymous::*,
266 send::SmtpCommandSendError,
267 };
268
269 fn domain() -> SmtpEhloDomain<'static> {
270 SmtpEhloDomain::SmtpDomain(SmtpDomain(Cow::Borrowed("example.com")))
271 }
272
273 #[test]
274 fn ir_success_does_not_send_ehlo_by_default() {
275 let opts = SmtpAuthAnonymousOptions::default();
276 let mut auth = SmtpAuthAnonymous::new(Some("trace@example.com"), domain(), opts);
277
278 let _ = expect_wants_write(&mut auth, None);
279 expect_wants_read(&mut auth);
280 expect_complete_ok(&mut auth, b"235 OK\r\n");
281 }
282
283 #[test]
284 fn ir_success_with_ehlo_returns_ok() {
285 let opts = SmtpAuthAnonymousOptions {
286 initial_request: true,
287 ensure_capabilities: true,
288 };
289 let mut auth = SmtpAuthAnonymous::new(Some("trace@example.com"), domain(), opts);
290
291 let _ = expect_wants_write(&mut auth, None);
292 expect_wants_read(&mut auth);
293 let _ehlo = expect_wants_write(&mut auth, Some(b"235 OK\r\n"));
294 expect_wants_read(&mut auth);
295 expect_complete_ok(&mut auth, b"250 server.example.com\r\n");
296 }
297
298 #[test]
299 fn ir_empty_trace_returns_ok() {
300 let opts = SmtpAuthAnonymousOptions {
301 initial_request: true,
302 ensure_capabilities: false,
303 };
304 let mut auth = SmtpAuthAnonymous::new(None, domain(), opts);
305
306 let bytes = expect_wants_write(&mut auth, None);
307 let line = from_utf8(&bytes).expect("utf8 command");
308 assert!(line.contains("AUTH ANONYMOUS"));
309
310 expect_wants_read(&mut auth);
311 expect_complete_ok(&mut auth, b"235 OK\r\n");
312 }
313
314 #[test]
315 fn non_ir_success_returns_ok() {
316 let opts = SmtpAuthAnonymousOptions {
317 initial_request: false,
318 ensure_capabilities: false,
319 };
320 let mut auth = SmtpAuthAnonymous::new(Some("trace@example.com"), domain(), opts);
321 let _ = expect_wants_write(&mut auth, None);
322 expect_wants_read(&mut auth);
323 let _ = expect_wants_write(&mut auth, Some(b"334 \r\n"));
324 expect_wants_read(&mut auth);
325 expect_complete_ok(&mut auth, b"235 OK\r\n");
326 }
327
328 #[test]
329 fn rejected_returns_rejected_error() {
330 let opts = SmtpAuthAnonymousOptions::default();
331 let mut auth = SmtpAuthAnonymous::new(Some("trace@example.com"), domain(), opts);
332 let _ = expect_wants_write(&mut auth, None);
333 expect_wants_read(&mut auth);
334
335 let err = expect_complete_err(&mut auth, b"535 anonymous disallowed\r\n");
336 let SmtpAuthAnonymousError::Rejected { code, message } = err else {
337 panic!("expected SmtpAuthAnonymousError::Rejected, got {err:?}");
338 };
339 assert_eq!(code, 535);
340 assert_eq!(message, "anonymous disallowed");
341 }
342
343 #[test]
344 fn eof_returns_eof_error() {
345 let opts = SmtpAuthAnonymousOptions::default();
346 let mut auth = SmtpAuthAnonymous::new(Some("trace@example.com"), domain(), opts);
347 let _ = expect_wants_write(&mut auth, None);
348 expect_wants_read(&mut auth);
349
350 let err = expect_complete_err(&mut auth, b"");
351 assert!(matches!(
352 err,
353 SmtpAuthAnonymousError::Send(SmtpCommandSendError::Eof)
354 ));
355 }
356
357 fn expect_wants_write(cor: &mut SmtpAuthAnonymous, arg: Option<&[u8]>) -> Vec<u8> {
358 match cor.resume(arg) {
359 SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => bytes,
360 state => panic!("expected WantsWrite, got {state:?}"),
361 }
362 }
363
364 fn expect_wants_read(cor: &mut SmtpAuthAnonymous) {
365 match cor.resume(None) {
366 SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {}
367 state => panic!("expected WantsRead, got {state:?}"),
368 }
369 }
370
371 fn expect_complete_ok(cor: &mut SmtpAuthAnonymous, reply: &[u8]) {
372 match cor.resume(Some(reply)) {
373 SmtpCoroutineState::Complete(Ok(())) => {}
374 state => panic!("expected Complete(Ok), got {state:?}"),
375 }
376 }
377
378 fn expect_complete_err(cor: &mut SmtpAuthAnonymous, reply: &[u8]) -> SmtpAuthAnonymousError {
379 match cor.resume(Some(reply)) {
380 SmtpCoroutineState::Complete(Err(err)) => err,
381 state => panic!("expected Complete(Err), got {state:?}"),
382 }
383 }
384}