io_smtp/rfc3207/
starttls.rs1use core::fmt;
45
46use alloc::{
47 string::{String, ToString},
48 vec::Vec,
49};
50
51use log::debug;
52use thiserror::Error;
53
54use crate::{coroutine::*, rfc5321::SmtpReplyCode, send::*, smtp_try};
55
56pub struct SmtpStartTlsCommand;
58
59impl From<SmtpStartTlsCommand> for Vec<u8> {
60 fn from(_: SmtpStartTlsCommand) -> Vec<u8> {
61 b"STARTTLS\r\n".to_vec()
62 }
63}
64
65#[derive(Clone, Debug, Error)]
67pub enum SmtpStartTlsError {
68 #[error("SMTP STARTTLS failed: rejected {code} {message}")]
70 Rejected {
71 code: u16,
73 message: String,
75 },
76 #[error("SMTP STARTTLS failed: {0}")]
78 Send(#[from] SmtpCommandSendError),
79}
80
81pub struct SmtpStartTls {
83 state: State,
84}
85
86impl SmtpStartTls {
87 pub fn new() -> Self {
89 Self {
90 state: State::Send(SmtpCommandSend::new(SmtpStartTlsCommand)),
91 }
92 }
93}
94
95impl Default for SmtpStartTls {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl SmtpCoroutine for SmtpStartTls {
102 type Yield = SmtpYield;
103 type Return = Result<Vec<u8>, SmtpStartTlsError>;
104
105 fn resume(&mut self, arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
106 match &mut self.state {
107 State::Send(send) => {
108 let out = smtp_try!(send, arg);
109
110 if out.response.code == SmtpReplyCode::SERVICE_READY {
111 debug!("starttls accepted, ready to upgrade");
112 return SmtpCoroutineState::Complete(Ok(Vec::new()));
113 }
114
115 let code = out.response.code.code();
116 let message = out.response.text().to_string();
117 SmtpCoroutineState::Complete(Err(SmtpStartTlsError::Rejected { code, message }))
118 }
119 }
120 }
121}
122
123enum State {
124 Send(SmtpCommandSend<SmtpStartTlsCommand>),
125}
126
127impl fmt::Display for State {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 match self {
130 Self::Send(_) => f.write_str("send starttls"),
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use alloc::vec::Vec;
138
139 use crate::{coroutine::*, rfc3207::starttls::*, send::SmtpCommandSendError};
140
141 #[test]
142 fn success_returns_empty_remaining() {
143 let mut starttls = SmtpStartTls::new();
144
145 let bytes = expect_wants_write(&mut starttls, None);
146 assert_eq!(bytes, b"STARTTLS\r\n");
147
148 expect_wants_read(&mut starttls);
149 let remaining = expect_complete_ok(&mut starttls, b"220 ready\r\n");
150 assert!(remaining.is_empty());
151 }
152
153 #[test]
154 fn rejected_returns_rejected_error() {
155 let mut starttls = SmtpStartTls::new();
156 let _ = expect_wants_write(&mut starttls, None);
157 expect_wants_read(&mut starttls);
158
159 let err = expect_complete_err(&mut starttls, b"454 TLS not available\r\n");
160 let SmtpStartTlsError::Rejected { code, message } = err else {
161 panic!("expected SmtpStartTlsError::Rejected, got {err:?}");
162 };
163 assert_eq!(code, 454);
164 assert_eq!(message, "TLS not available");
165 }
166
167 #[test]
168 fn eof_returns_eof_error() {
169 let mut starttls = SmtpStartTls::new();
170 let _ = expect_wants_write(&mut starttls, None);
171 expect_wants_read(&mut starttls);
172
173 let err = expect_complete_err(&mut starttls, b"");
174 assert!(matches!(
175 err,
176 SmtpStartTlsError::Send(SmtpCommandSendError::Eof)
177 ));
178 }
179
180 fn expect_wants_write(cor: &mut SmtpStartTls, arg: Option<&[u8]>) -> Vec<u8> {
181 match cor.resume(arg) {
182 SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => bytes,
183 state => panic!("expected WantsWrite, got {state:?}"),
184 }
185 }
186
187 fn expect_wants_read(cor: &mut SmtpStartTls) {
188 match cor.resume(None) {
189 SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {}
190 state => panic!("expected WantsRead, got {state:?}"),
191 }
192 }
193
194 fn expect_complete_ok(cor: &mut SmtpStartTls, reply: &[u8]) -> Vec<u8> {
195 match cor.resume(Some(reply)) {
196 SmtpCoroutineState::Complete(Ok(remaining)) => remaining,
197 state => panic!("expected Complete(Ok), got {state:?}"),
198 }
199 }
200
201 fn expect_complete_err(cor: &mut SmtpStartTls, reply: &[u8]) -> SmtpStartTlsError {
202 match cor.resume(Some(reply)) {
203 SmtpCoroutineState::Complete(Err(err)) => err,
204 state => panic!("expected Complete(Err), got {state:?}"),
205 }
206 }
207}