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