1use core::{fmt, mem};
50
51use alloc::{
52 borrow::Cow,
53 string::{String, ToString},
54 vec::Vec,
55};
56
57use imap_codec::{
58 AuthenticateDataCodec, CommandCodec,
59 fragmentizer::Fragmentizer,
60 imap_types::{
61 auth::{AuthMechanism, AuthenticateData},
62 command::{Command, CommandBody},
63 core::{IString, NString, TagGenerator},
64 response::{Capability, Code, Data, StatusBody, StatusKind, Tagged},
65 secret::Secret,
66 },
67};
68use log::{debug, trace};
69use thiserror::Error;
70
71use crate::{coroutine::*, imap_try, rfc2971::id::*, rfc3501::capability::*, send::*};
72
73#[derive(Clone, Debug, Error)]
75pub enum ImapAuthAnonymousError {
76 #[error("IMAP AUTHENTICATE ANONYMOUS failed: NO {0}")]
78 No(String),
79 #[error("IMAP AUTHENTICATE ANONYMOUS failed: BAD {0}")]
81 Bad(String),
82 #[error("IMAP AUTHENTICATE ANONYMOUS failed: BYE {0}")]
84 Bye(String),
85 #[error("IMAP AUTHENTICATE ANONYMOUS failed: server did not return a tagged response")]
87 MissingTagged,
88 #[error(
90 "IMAP AUTHENTICATE ANONYMOUS failed: server did not send the expected continuation request"
91 )]
92 ExpectedContinuationRequest,
93 #[error("IMAP AUTHENTICATE ANONYMOUS failed: server sent an unexpected continuation request")]
95 UnexpectedContinuationRequest,
96 #[error(
98 "IMAP AUTHENTICATE ANONYMOUS failed: server returned OK before the mechanism could complete"
99 )]
100 UnexpectedOk,
101 #[error("IMAP AUTHENTICATE ANONYMOUS failed: {0}")]
103 Send(#[from] ImapSendError),
104 #[error(transparent)]
106 Capability(#[from] ImapCapabilityGetError),
107 #[error(transparent)]
109 ServerId(#[from] ImapServerIdError),
110}
111
112#[derive(Clone, Debug, Default, Eq, PartialEq)]
114pub struct ImapAuthAnonymousOptions {
115 pub initial_request: bool,
118 pub ensure_capabilities: bool,
121 pub auto_id: Option<Vec<(IString<'static>, NString<'static>)>>,
126}
127
128pub struct ImapAuthAnonymous {
130 state: State,
131 observed: Vec<Capability<'static>>,
132 opts: ImapAuthAnonymousOptions,
133}
134
135impl ImapAuthAnonymous {
136 pub fn new(message: Option<impl AsRef<str>>, opts: ImapAuthAnonymousOptions) -> Self {
143 let payload = message
144 .map(|m| m.as_ref().as_bytes().to_vec())
145 .unwrap_or_default();
146 let tag = TagGenerator::new().generate();
147
148 let state = if opts.initial_request {
149 let body = CommandBody::Authenticate {
150 mechanism: AuthMechanism::try_from("ANONYMOUS").unwrap(),
152 initial_response: Some(Secret::new(payload.into())),
153 };
154 let cmd = Command { tag, body };
155 trace!("send IMAP command {cmd:?}");
156 State::SendIr(ImapSend::new(CommandCodec::new(), cmd))
157 } else {
158 let body = CommandBody::Authenticate {
159 mechanism: AuthMechanism::try_from("ANONYMOUS").unwrap(),
161 initial_response: None,
162 };
163 let cmd = Command { tag, body };
164 trace!("send IMAP command {cmd:?}");
165 State::Send {
166 send: ImapSend::new(CommandCodec::new(), cmd),
167 payload: payload.into(),
168 }
169 };
170
171 Self {
172 state,
173 observed: Vec::new(),
174 opts,
175 }
176 }
177
178 fn wants_capability(
179 &mut self,
180 code: Option<Code<'static>>,
181 data: Vec<Data<'static>>,
182 untagged: Vec<StatusBody<'static>>,
183 ) -> Option<State> {
184 let mut new_capability = None;
185
186 if let Some(Code::Capability(capability)) = code {
187 new_capability.replace(capability);
188 }
189
190 for data in data {
191 if let Data::Capability(capability) = data {
192 new_capability.replace(capability);
193 }
194 }
195
196 for StatusBody { code, .. } in untagged {
197 if let Some(Code::Capability(capability)) = code {
198 new_capability.replace(capability);
199 }
200 }
201
202 if let Some(capability) = new_capability {
203 self.observed = capability.into_iter().collect();
204 }
205
206 (self.opts.ensure_capabilities && self.observed.is_empty())
207 .then(|| State::Capability(ImapCapabilityGet::new()))
208 }
209
210 fn wants_id(&mut self) -> Option<State> {
211 let params = self.opts.auto_id.take()?;
212 let wire = (!params.is_empty()).then_some(params);
213 Some(State::Id(ImapServerId::new(ImapServerIdOptions {
214 parameters: wire,
215 })))
216 }
217}
218
219impl ImapCoroutine for ImapAuthAnonymous {
220 type Yield = ImapYield;
221 type Return = Result<Vec<Capability<'static>>, ImapAuthAnonymousError>;
222
223 fn resume(
224 &mut self,
225 fragmentizer: &mut Fragmentizer,
226 arg: Option<&[u8]>,
227 ) -> ImapCoroutineState<Self::Yield, Self::Return> {
228 loop {
229 match &mut self.state {
230 State::Send { send, payload } => {
231 let out = imap_try!(send, fragmentizer, arg);
232
233 if let Some(bye) = out.bye {
234 let err = ImapAuthAnonymousError::Bye(bye.text.to_string());
235 return ImapCoroutineState::Complete(Err(err));
236 }
237
238 if out.continuation_request.is_some() {
239 let payload = mem::take(payload).into_owned();
240 let auth = AuthenticateData::r#continue(payload);
241 let codec = AuthenticateDataCodec::new();
242 self.state = State::Continue(ImapSend::new(codec, auth));
243 debug!("{}", self.state);
244 continue;
245 }
246
247 if let Some(Tagged { body, .. }) = out.tagged {
248 let err = match body.kind {
249 StatusKind::Ok => ImapAuthAnonymousError::UnexpectedOk,
250 StatusKind::No => ImapAuthAnonymousError::No(body.text.to_string()),
251 StatusKind::Bad => ImapAuthAnonymousError::Bad(body.text.to_string()),
252 };
253
254 return ImapCoroutineState::Complete(Err(err));
255 }
256
257 let err = ImapAuthAnonymousError::ExpectedContinuationRequest;
258 return ImapCoroutineState::Complete(Err(err));
259 }
260 State::SendIr(send) => {
261 let out = imap_try!(send, fragmentizer, arg);
262
263 if let Some(bye) = out.bye {
264 let err = ImapAuthAnonymousError::Bye(bye.text.to_string());
265 return ImapCoroutineState::Complete(Err(err));
266 }
267
268 if out.continuation_request.is_some() {
269 let err = ImapAuthAnonymousError::UnexpectedContinuationRequest;
270 return ImapCoroutineState::Complete(Err(err));
271 }
272
273 let Some(Tagged { body, .. }) = out.tagged else {
274 let err = ImapAuthAnonymousError::MissingTagged;
275 return ImapCoroutineState::Complete(Err(err));
276 };
277
278 let code = match body.kind {
279 StatusKind::Ok => body.code,
280 StatusKind::No => {
281 let err = ImapAuthAnonymousError::No(body.text.to_string());
282 return ImapCoroutineState::Complete(Err(err));
283 }
284 StatusKind::Bad => {
285 let err = ImapAuthAnonymousError::Bad(body.text.to_string());
286 return ImapCoroutineState::Complete(Err(err));
287 }
288 };
289
290 if let Some(next) = self.wants_capability(code, out.data, out.untagged) {
291 self.state = next;
292 debug!("{}", self.state);
293 continue;
294 }
295
296 if let Some(next) = self.wants_id() {
297 self.state = next;
298 debug!("{}", self.state);
299 continue;
300 }
301
302 let capability = mem::take(&mut self.observed);
303 return ImapCoroutineState::Complete(Ok(capability));
304 }
305 State::Continue(send) => {
306 let out = imap_try!(send, fragmentizer, arg);
307
308 if let Some(bye) = out.bye {
309 let err = ImapAuthAnonymousError::Bye(bye.text.to_string());
310 return ImapCoroutineState::Complete(Err(err));
311 }
312
313 if out.continuation_request.is_some() {
314 let err = ImapAuthAnonymousError::UnexpectedContinuationRequest;
315 return ImapCoroutineState::Complete(Err(err));
316 }
317
318 let Some(Tagged { body, .. }) = out.tagged else {
319 let err = ImapAuthAnonymousError::MissingTagged;
320 return ImapCoroutineState::Complete(Err(err));
321 };
322
323 let code = match body.kind {
324 StatusKind::Ok => body.code,
325 StatusKind::No => {
326 let err = ImapAuthAnonymousError::No(body.text.to_string());
327 return ImapCoroutineState::Complete(Err(err));
328 }
329 StatusKind::Bad => {
330 let err = ImapAuthAnonymousError::Bad(body.text.to_string());
331 return ImapCoroutineState::Complete(Err(err));
332 }
333 };
334
335 if let Some(next) = self.wants_capability(code, out.data, out.untagged) {
336 self.state = next;
337 debug!("{}", self.state);
338 continue;
339 }
340
341 if let Some(next) = self.wants_id() {
342 self.state = next;
343 debug!("{}", self.state);
344 continue;
345 }
346
347 let capability = mem::take(&mut self.observed);
348 return ImapCoroutineState::Complete(Ok(capability));
349 }
350 State::Capability(capability) => {
351 self.observed = imap_try!(capability, fragmentizer, arg);
352
353 if let Some(next) = self.wants_id() {
354 self.state = next;
355 debug!("{}", self.state);
356 continue;
357 }
358
359 let capability = mem::take(&mut self.observed);
360 return ImapCoroutineState::Complete(Ok(capability));
361 }
362 State::Id(id) => {
363 imap_try!(id, fragmentizer, arg);
364 let capability = mem::take(&mut self.observed);
365 return ImapCoroutineState::Complete(Ok(capability));
366 }
367 }
368 }
369 }
370}
371
372enum State {
373 Send {
374 send: ImapSend<CommandCodec>,
375 payload: Cow<'static, [u8]>,
376 },
377 SendIr(ImapSend<CommandCodec>),
378 Continue(ImapSend<AuthenticateDataCodec>),
379 Capability(ImapCapabilityGet),
380 Id(ImapServerId),
381}
382
383impl fmt::Display for State {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 match self {
386 Self::Send { .. } => f.write_str("send auth"),
387 Self::SendIr(_) => f.write_str("send auth with ir"),
388 Self::Continue(_) => f.write_str("send trace"),
389 Self::Capability(_) => f.write_str("fetch capabilities"),
390 Self::Id(_) => f.write_str("send id"),
391 }
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use core::str;
398
399 use alloc::format;
400
401 use crate::sasl::auth_anonymous::*;
402
403 #[test]
404 fn ir_success_returns_ok() {
405 let opts = ImapAuthAnonymousOptions {
406 initial_request: true,
407 ..Default::default()
408 };
409
410 let mut auth = ImapAuthAnonymous::new(Some("trace@example.org"), opts);
411 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
412
413 let bytes = expect_wants_write(&mut auth, &mut frag, None);
414 let line = str::from_utf8(&bytes).expect("utf8 command");
415 let tag = first_word(line);
416 assert!(line.contains("AUTHENTICATE ANONYMOUS "));
417
418 expect_wants_read(&mut auth, &mut frag);
419
420 let reply = format!("{tag} OK AUTHENTICATE completed\r\n");
421 expect_complete_ok(&mut auth, &mut frag, reply.as_bytes());
422 }
423
424 #[test]
425 fn ir_rejected_returns_no_error() {
426 let opts = ImapAuthAnonymousOptions {
427 initial_request: true,
428 ..Default::default()
429 };
430
431 let mut auth = ImapAuthAnonymous::new(None::<&str>, opts);
432 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
433
434 let bytes = expect_wants_write(&mut auth, &mut frag, None);
435 let tag = first_word(str::from_utf8(&bytes).expect("utf8 command"));
436
437 expect_wants_read(&mut auth, &mut frag);
438
439 let reply = format!("{tag} NO anonymous access disabled\r\n");
440 let err = expect_complete_err(&mut auth, &mut frag, reply.as_bytes());
441 let ImapAuthAnonymousError::No(text) = err else {
442 panic!("expected ImapAuthAnonymousError::No, got {err:?}");
443 };
444 assert_eq!(text, "anonymous access disabled");
445 }
446
447 #[test]
448 fn ir_tagged_bad_returns_bad_error() {
449 let opts = ImapAuthAnonymousOptions {
450 initial_request: true,
451 ..Default::default()
452 };
453
454 let mut auth = ImapAuthAnonymous::new(None::<&str>, opts);
455 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
456
457 let bytes = expect_wants_write(&mut auth, &mut frag, None);
458 let tag = first_word(str::from_utf8(&bytes).expect("utf8 command"));
459
460 expect_wants_read(&mut auth, &mut frag);
461
462 let reply = format!("{tag} BAD AUTHENTICATE not enabled\r\n");
463 let err = expect_complete_err(&mut auth, &mut frag, reply.as_bytes());
464 let ImapAuthAnonymousError::Bad(text) = err else {
465 panic!("expected ImapAuthAnonymousError::Bad, got {err:?}");
466 };
467 assert_eq!(text, "AUTHENTICATE not enabled");
468 }
469
470 #[test]
471 fn non_ir_success_returns_ok() {
472 let opts = ImapAuthAnonymousOptions::default();
473 let mut auth = ImapAuthAnonymous::new(None::<&str>, opts);
474 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
475
476 let bytes = expect_wants_write(&mut auth, &mut frag, None);
477 let line = str::from_utf8(&bytes).expect("utf8 command");
478 let tag = first_word(line);
479 assert!(line.trim_end().ends_with("AUTHENTICATE ANONYMOUS"));
480
481 expect_wants_read(&mut auth, &mut frag);
482
483 let trace = expect_wants_write(&mut auth, &mut frag, Some(b"+ \r\n"));
484 assert!(trace.ends_with(b"\r\n"));
485
486 expect_wants_read(&mut auth, &mut frag);
487
488 let reply = format!("{tag} OK AUTHENTICATE completed\r\n");
489 expect_complete_ok(&mut auth, &mut frag, reply.as_bytes());
490 }
491
492 #[test]
493 fn non_ir_rejected_returns_no_error() {
494 let opts = ImapAuthAnonymousOptions::default();
495 let mut auth = ImapAuthAnonymous::new(Some("trace@example.org"), opts);
496 let mut frag = Fragmentizer::new(50 * 1024 * 1024);
497
498 let bytes = expect_wants_write(&mut auth, &mut frag, None);
499 let tag = first_word(str::from_utf8(&bytes).expect("utf8 command"));
500
501 expect_wants_read(&mut auth, &mut frag);
502 expect_wants_write(&mut auth, &mut frag, Some(b"+ \r\n"));
503 expect_wants_read(&mut auth, &mut frag);
504
505 let reply = format!("{tag} NO anonymous access disabled\r\n");
506 let err = expect_complete_err(&mut auth, &mut frag, reply.as_bytes());
507 let ImapAuthAnonymousError::No(text) = err else {
508 panic!("expected ImapAuthAnonymousError::No, got {err:?}");
509 };
510 assert_eq!(text, "anonymous access disabled");
511 }
512
513 fn expect_wants_write(
514 cor: &mut ImapAuthAnonymous,
515 frag: &mut Fragmentizer,
516 arg: Option<&[u8]>,
517 ) -> Vec<u8> {
518 match cor.resume(frag, arg) {
519 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => bytes,
520 state => panic!("expected WantsWrite, got {state:?}"),
521 }
522 }
523
524 fn expect_wants_read(cor: &mut ImapAuthAnonymous, frag: &mut Fragmentizer) {
525 match cor.resume(frag, None) {
526 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {}
527 state => panic!("expected WantsRead, got {state:?}"),
528 }
529 }
530
531 fn expect_complete_ok(cor: &mut ImapAuthAnonymous, frag: &mut Fragmentizer, reply: &[u8]) {
532 match cor.resume(frag, Some(reply)) {
533 ImapCoroutineState::Complete(Ok(_)) => {}
534 state => panic!("expected Complete(Ok), got {state:?}"),
535 }
536 }
537
538 fn expect_complete_err(
539 cor: &mut ImapAuthAnonymous,
540 frag: &mut Fragmentizer,
541 reply: &[u8],
542 ) -> ImapAuthAnonymousError {
543 match cor.resume(frag, Some(reply)) {
544 ImapCoroutineState::Complete(Err(err)) => err,
545 state => panic!("expected Complete(Err), got {state:?}"),
546 }
547 }
548
549 fn first_word(line: &str) -> &str {
550 line.split_whitespace()
551 .next()
552 .expect("first whitespace-separated token")
553 }
554}