io_imap/client/
connect.rs1use core::{any::Any, time::Duration};
11
12use alloc::vec::Vec;
13
14use std::io::{self, Read, Write};
15
16use imap_codec::{fragmentizer::Fragmentizer, imap_types::response::Capability};
17use io_sasl::mechanism::Sasl;
18#[cfg(feature = "scram")]
19use io_sasl::rfc5802::SaslScramCreds;
20use pimalaya_stream::{
21 retry::Retry,
22 stream::{Stream, TcpConnectOptions, TlsConnectOptions, UnixConnectOptions},
23 tls::Tls,
24};
25#[cfg(feature = "scram")]
26use rand::{RngExt, distr::Alphanumeric};
27use url::Url;
28
29use crate::{
30 client::{
31 FRAGMENTIZER_MAX_MESSAGE_SIZE, ImapClientError, ImapClientStd, ImapStream, READ_BUFFER_SIZE,
32 },
33 coroutine::*,
34 session::*,
35};
36
37impl ImapStream for Stream {
38 fn as_any_mut(&mut self) -> &mut dyn Any {
39 self
40 }
41
42 fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
43 Stream::set_read_timeout(self, timeout)
44 }
45
46 fn stop_retrying(&mut self) {
47 self.retry = Retry::Never;
48 }
49}
50
51impl ImapClientStd {
52 pub fn connect(
68 url: &Url,
69 tls: &Tls,
70 sasl: Option<impl Into<Sasl>>,
71 opts: ImapSessionOpenOptions,
72 ) -> Result<(Self, Vec<Capability<'static>>), ImapClientError> {
73 let transport = ImapSessionTransport::from_url(url)?;
74 let sasl = sasl.map(Into::into).map(with_client_nonce);
75 let mut session = ImapSessionOpen::new(transport, sasl, opts);
76 let mut fragmentizer = Fragmentizer::new(FRAGMENTIZER_MAX_MESSAGE_SIZE);
77 let mut stream: Option<Stream> = None;
78 let mut buf = [0u8; READ_BUFFER_SIZE];
79 let mut arg: Option<&[u8]> = None;
80
81 let missing = || io::Error::other("IMAP session yielded I/O before connecting");
85
86 loop {
87 match session.resume(&mut fragmentizer, arg.take()) {
88 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
89 ImapCoroutineState::Complete(Ok(data)) => {
90 let stream = stream.ok_or_else(missing)?;
91 let mut client = Self::new(stream);
92 client.fragmentizer = fragmentizer;
93 client.pre_authenticated = data.pre_authenticated;
94 return Ok((client, data.capability));
95 }
96 ImapCoroutineState::Yielded(ImapSessionOpenYield::WantsTcpConnect {
97 host,
98 port,
99 }) => {
100 let opts = TcpConnectOptions::default();
101 stream = Some(Stream::connect_tcp(host, port, opts)?);
102 }
103 ImapCoroutineState::Yielded(ImapSessionOpenYield::WantsTlsConnect {
104 host,
105 port,
106 }) => {
107 let opts = TlsConnectOptions {
108 tls: tls.clone(),
109 ..Default::default()
110 };
111
112 stream = Some(Stream::connect_tls(host, port, opts)?);
113 }
114 ImapCoroutineState::Yielded(ImapSessionOpenYield::WantsUnixConnect(path)) => {
115 let opts = UnixConnectOptions::default();
116 stream = Some(Stream::connect_unix(path, opts)?);
117 }
118 ImapCoroutineState::Yielded(ImapSessionOpenYield::WantsTlsUpgrade) => {
119 let plain = stream.take().ok_or_else(missing)?;
120 stream = Some(plain.upgrade_tls(tls)?);
121 }
122 ImapCoroutineState::Yielded(ImapSessionOpenYield::WantsRead) => {
123 let stream = stream.as_mut().ok_or_else(missing)?;
124 let n = match stream.read(&mut buf)? {
125 0 => {
126 let kind = io::ErrorKind::UnexpectedEof;
127 let err = "IMAP server closed the connection";
128 return Err(io::Error::new(kind, err).into());
129 }
130 n => n,
131 };
132
133 arg = Some(&buf[..n]);
134 }
135 ImapCoroutineState::Yielded(ImapSessionOpenYield::WantsWrite(bytes)) => {
136 let stream = stream.as_mut().ok_or_else(missing)?;
137 stream.write_all(&bytes)?;
138 }
139 }
140 }
141 }
142}
143
144#[cfg(feature = "scram")]
151fn with_client_nonce(sasl: Sasl) -> Sasl {
152 match sasl {
153 Sasl::ScramSha256(creds) if creds.nonce.is_empty() => {
154 let nonce = rand::rng().sample_iter(Alphanumeric).take(24).collect();
155 Sasl::ScramSha256(SaslScramCreds { nonce, ..creds })
156 }
157 sasl => sasl,
158 }
159}
160
161#[cfg(not(feature = "scram"))]
163fn with_client_nonce(sasl: Sasl) -> Sasl {
164 sasl
165}