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