1#![doc = include_str!("../README.md")]
8#![deny(rust_2018_idioms)]
9
10use std::borrow::Cow;
11use std::fmt::Display;
12
13pub mod request;
14pub mod response;
15mod tokens;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(
20 feature = "rkyv",
21 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
22)]
23pub enum Request<T> {
24 Ehlo { host: T },
25 Lhlo { host: T },
26 Helo { host: T },
27 Mail { from: MailFrom<T> },
28 Rcpt { to: RcptTo<T> },
29 Bdat { chunk_size: usize, is_last: bool },
30 Auth { mechanism: u64, initial_response: T },
31 Noop { value: T },
32 Vrfy { value: T },
33 Expn { value: T },
34 Help { value: T },
35 Etrn { name: T },
36 Atrn { domains: Vec<T> },
37 Burl { uri: T, is_last: bool },
38 StartTls,
39 Data,
40 Rset,
41 Quit,
42}
43
44impl<T> Request<T> {
45 pub fn name(&self) -> &'static str {
47 match self {
48 Request::Ehlo { .. } => "EHLO",
49 Request::Helo { .. } => "HELO",
50 Request::Lhlo { .. } => "LHLO",
51 Request::Mail { .. } => "MAIL",
52 Request::Rcpt { .. } => "RCPT",
53 Request::Data => "DATA",
54 Request::Bdat { .. } => "BDAT",
55 Request::Auth { .. } => "AUTH",
56 Request::Noop { .. } => "NOOP",
57 Request::Vrfy { .. } => "VRFY",
58 Request::Expn { .. } => "EXPN",
59 Request::Help { .. } => "HELP",
60 Request::Etrn { .. } => "ETRN",
61 Request::Atrn { .. } => "ATRN",
62 Request::Burl { .. } => "BURL",
63 Request::StartTls => "STARTTLS",
64 Request::Rset => "RSET",
65 Request::Quit => "QUIT",
66 }
67 }
68}
69
70impl Request<Cow<'_, str>> {
71 pub fn into_owned(self) -> Request<String> {
72 match self {
73 Request::Ehlo { host } => Request::Ehlo {
74 host: host.into_owned(),
75 },
76 Request::Lhlo { host } => Request::Lhlo {
77 host: host.into_owned(),
78 },
79 Request::Helo { host } => Request::Helo {
80 host: host.into_owned(),
81 },
82 Request::Mail { from } => Request::Mail {
83 from: from.into_owned(),
84 },
85 Request::Rcpt { to } => Request::Rcpt {
86 to: to.into_owned(),
87 },
88 Request::Bdat {
89 chunk_size,
90 is_last,
91 } => Request::Bdat {
92 chunk_size,
93 is_last,
94 },
95 Request::Auth {
96 mechanism,
97 initial_response,
98 } => Request::Auth {
99 mechanism,
100 initial_response: initial_response.into_owned(),
101 },
102 Request::Noop { value } => Request::Noop {
103 value: value.into_owned(),
104 },
105 Request::Vrfy { value } => Request::Vrfy {
106 value: value.into_owned(),
107 },
108 Request::Expn { value } => Request::Expn {
109 value: value.into_owned(),
110 },
111 Request::Help { value } => Request::Help {
112 value: value.into_owned(),
113 },
114 Request::Etrn { name } => Request::Etrn {
115 name: name.into_owned(),
116 },
117 Request::Atrn { domains } => Request::Atrn {
118 domains: domains.into_iter().map(Cow::into_owned).collect(),
119 },
120 Request::Burl { uri, is_last } => Request::Burl {
121 uri: uri.into_owned(),
122 is_last,
123 },
124 Request::StartTls => Request::StartTls,
125 Request::Data => Request::Data,
126 Request::Rset => Request::Rset,
127 Request::Quit => Request::Quit,
128 }
129 }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134#[cfg_attr(
135 feature = "rkyv",
136 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
137)]
138pub struct MailFrom<T> {
139 pub address: T,
140 pub flags: u64,
141 pub size: usize,
142 pub trans_id: Option<T>,
143 pub by: i64,
144 pub env_id: Option<T>,
145 pub solicit: Option<T>,
146 pub mtrk: Option<Mtrk<T>>,
147 pub auth: Option<T>,
148 pub hold_for: u64,
149 pub hold_until: u64,
150 pub mt_priority: i64,
151}
152
153impl MailFrom<Cow<'_, str>> {
154 pub fn into_owned(self) -> MailFrom<String> {
155 MailFrom {
156 address: self.address.into_owned(),
157 flags: self.flags,
158 size: self.size,
159 trans_id: self.trans_id.map(Cow::into_owned),
160 by: self.by,
161 env_id: self.env_id.map(Cow::into_owned),
162 solicit: self.solicit.map(Cow::into_owned),
163 mtrk: self.mtrk.map(Mtrk::into_owned),
164 auth: self.auth.map(Cow::into_owned),
165 hold_for: self.hold_for,
166 hold_until: self.hold_until,
167 mt_priority: self.mt_priority,
168 }
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174#[cfg_attr(
175 feature = "rkyv",
176 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
177)]
178pub struct RcptTo<T> {
179 pub address: T,
180 pub orcpt: Option<T>,
181 pub rrvs: i64,
182 pub flags: u64,
183}
184
185impl RcptTo<Cow<'_, str>> {
186 pub fn into_owned(self) -> RcptTo<String> {
187 RcptTo {
188 address: self.address.into_owned(),
189 orcpt: self.orcpt.map(Cow::into_owned),
190 rrvs: self.rrvs,
191 flags: self.flags,
192 }
193 }
194}
195
196pub const MAIL_BODY_7BIT: u64 = 1 << 0;
197pub const MAIL_BODY_8BITMIME: u64 = 1 << 1;
198pub const MAIL_BODY_BINARYMIME: u64 = 1 << 2;
199pub const MAIL_RET_FULL: u64 = 1 << 3;
200pub const MAIL_RET_HDRS: u64 = 1 << 4;
201pub const MAIL_SMTPUTF8: u64 = 1 << 5;
202pub const MAIL_REQUIRETLS: u64 = 1 << 6;
203pub const MAIL_CONPERM: u64 = 1 << 7;
204pub const MAIL_BY_NOTIFY: u64 = 1 << 8;
205pub const MAIL_BY_RETURN: u64 = 1 << 9;
206pub const MAIL_BY_TRACE: u64 = 1 << 10;
207
208pub const RCPT_NOTIFY_SUCCESS: u64 = 1 << 0;
209pub const RCPT_NOTIFY_FAILURE: u64 = 1 << 1;
210pub const RCPT_NOTIFY_DELAY: u64 = 1 << 2;
211pub const RCPT_NOTIFY_NEVER: u64 = 1 << 3;
212pub const RCPT_CONNEG: u64 = 1 << 4;
213pub const RCPT_RRVS_REJECT: u64 = 1 << 5;
214pub const RCPT_RRVS_CONTINUE: u64 = 1 << 6;
215
216#[derive(Debug, Clone, PartialEq, Eq)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
218#[cfg_attr(
219 feature = "rkyv",
220 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
221)]
222pub struct Mtrk<T> {
223 pub certifier: T,
224 pub timeout: u64,
225}
226
227impl Mtrk<Cow<'_, str>> {
228 pub fn into_owned(self) -> Mtrk<String> {
229 Mtrk {
230 certifier: self.certifier.into_owned(),
231 timeout: self.timeout,
232 }
233 }
234}
235
236pub const AUTH_SCRAM_SHA_256_PLUS: u64 = 1u64 << 0;
237pub const AUTH_SCRAM_SHA_256: u64 = 1u64 << 1;
238pub const AUTH_SCRAM_SHA_1_PLUS: u64 = 1u64 << 2;
239pub const AUTH_SCRAM_SHA_1: u64 = 1u64 << 3;
240pub const AUTH_OAUTHBEARER: u64 = 1u64 << 4;
241pub const AUTH_XOAUTH: u64 = 1u64 << 5;
242pub const AUTH_XOAUTH2: u64 = 1u64 << 6;
243pub const AUTH_9798_M_DSA_SHA1: u64 = 1u64 << 7;
244pub const AUTH_9798_M_ECDSA_SHA1: u64 = 1u64 << 8;
245pub const AUTH_9798_M_RSA_SHA1_ENC: u64 = 1u64 << 9;
246pub const AUTH_9798_U_DSA_SHA1: u64 = 1u64 << 10;
247pub const AUTH_9798_U_ECDSA_SHA1: u64 = 1u64 << 11;
248pub const AUTH_9798_U_RSA_SHA1_ENC: u64 = 1u64 << 12;
249pub const AUTH_EAP_AES128: u64 = 1u64 << 13;
250pub const AUTH_EAP_AES128_PLUS: u64 = 1u64 << 14;
251pub const AUTH_ECDH_X25519_CHALLENGE: u64 = 1u64 << 15;
252pub const AUTH_ECDSA_NIST256P_CHALLENGE: u64 = 1u64 << 16;
253pub const AUTH_EXTERNAL: u64 = 1u64 << 17;
254pub const AUTH_GS2_KRB5: u64 = 1u64 << 18;
255pub const AUTH_GS2_KRB5_PLUS: u64 = 1u64 << 19;
256pub const AUTH_GSS_SPNEGO: u64 = 1u64 << 20;
257pub const AUTH_GSSAPI: u64 = 1u64 << 21;
258pub const AUTH_KERBEROS_V4: u64 = 1u64 << 22;
259pub const AUTH_KERBEROS_V5: u64 = 1u64 << 23;
260pub const AUTH_NMAS_SAMBA_AUTH: u64 = 1u64 << 24;
261pub const AUTH_NMAS_AUTHEN: u64 = 1u64 << 25;
262pub const AUTH_NMAS_LOGIN: u64 = 1u64 << 26;
263pub const AUTH_NTLM: u64 = 1u64 << 27;
264pub const AUTH_OAUTH10A: u64 = 1u64 << 28;
265pub const AUTH_OPENID20: u64 = 1u64 << 29;
266pub const AUTH_OTP: u64 = 1u64 << 30;
267pub const AUTH_SAML20: u64 = 1u64 << 31;
268pub const AUTH_SECURID: u64 = 1u64 << 32;
269pub const AUTH_SKEY: u64 = 1u64 << 33;
270pub const AUTH_SPNEGO: u64 = 1u64 << 34;
271pub const AUTH_SPNEGO_PLUS: u64 = 1u64 << 35;
272pub const AUTH_SXOVER_PLUS: u64 = 1u64 << 36;
273pub const AUTH_CRAM_MD5: u64 = 1u64 << 37;
274pub const AUTH_DIGEST_MD5: u64 = 1u64 << 38;
275pub const AUTH_LOGIN: u64 = 1u64 << 39;
276pub const AUTH_PLAIN: u64 = 1u64 << 40;
277pub const AUTH_ANONYMOUS: u64 = 1u64 << 41;
278
279pub const EXT_8BIT_MIME: u32 = 1 << 0;
280pub const EXT_ATRN: u32 = 1 << 1;
281pub const EXT_AUTH: u32 = 1 << 2;
282pub const EXT_BINARY_MIME: u32 = 1 << 3;
283pub const EXT_BURL: u32 = 1 << 4;
284pub const EXT_CHECKPOINT: u32 = 1 << 5;
285pub const EXT_CHUNKING: u32 = 1 << 6;
286pub const EXT_CONNEG: u32 = 1 << 7;
287pub const EXT_CONPERM: u32 = 1 << 8;
288pub const EXT_DELIVER_BY: u32 = 1 << 9;
289pub const EXT_DSN: u32 = 1 << 10;
290pub const EXT_ENHANCED_STATUS_CODES: u32 = 1 << 11;
291pub const EXT_ETRN: u32 = 1 << 12;
292pub const EXT_FUTURE_RELEASE: u32 = 1 << 13;
293pub const EXT_HELP: u32 = 1 << 14;
294pub const EXT_MT_PRIORITY: u32 = 1 << 15;
295pub const EXT_MTRK: u32 = 1 << 16;
296pub const EXT_NO_SOLICITING: u32 = 1 << 17;
297pub const EXT_ONEX: u32 = 1 << 18;
298pub const EXT_PIPELINING: u32 = 1 << 19;
299pub const EXT_REQUIRE_TLS: u32 = 1 << 20;
300pub const EXT_RRVS: u32 = 1 << 21;
301pub const EXT_SIZE: u32 = 1 << 22;
302pub const EXT_SMTP_UTF8: u32 = 1 << 23;
303pub const EXT_START_TLS: u32 = 1 << 24;
304pub const EXT_VERB: u32 = 1 << 25;
305pub const EXT_EXPN: u32 = 1 << 26;
306pub const EXT_VRFY: u32 = 1 << 27;
307
308#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
309#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
310#[cfg_attr(
311 feature = "rkyv",
312 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
313)]
314pub enum MtPriority {
315 #[default]
316 Mixer,
317 Stanag4406,
318 Nsep,
319}
320
321#[derive(Debug, Default, Clone, PartialEq, Eq)]
322#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
323#[cfg_attr(
324 feature = "rkyv",
325 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
326)]
327pub struct EhloResponse<T: Display> {
328 pub hostname: T,
329 pub capabilities: u32,
330
331 pub auth_mechanisms: u64,
332 pub deliver_by: u64,
333 pub future_release_interval: u64,
334 pub future_release_datetime: u64,
335 pub mt_priority: MtPriority,
336 pub no_soliciting: Option<String>,
337 pub size: usize,
338}
339
340#[derive(Debug, Clone, PartialEq, Eq)]
341#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
342#[cfg_attr(
343 feature = "rkyv",
344 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
345)]
346pub struct Response<T: Display> {
347 pub code: u16,
348 pub esc: [u8; 3],
349 pub message: T,
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq)]
353#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
354#[cfg_attr(
355 feature = "rkyv",
356 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
357)]
358pub enum Severity {
359 PositiveCompletion = 2,
360 PositiveIntermediate = 3,
361 TransientNegativeCompletion = 4,
362 PermanentNegativeCompletion = 5,
363 Invalid = 0,
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq)]
367#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
368#[cfg_attr(
369 feature = "rkyv",
370 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
371)]
372pub enum Category {
373 Syntax = 0,
374 Information = 1,
375 Connections = 2,
376 Unspecified3 = 3,
377 Unspecified4 = 4,
378 MailSystem = 5,
379 Invalid = 6,
380}
381
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub enum Error {
384 NeedsMoreData { bytes_left: usize },
385 UnknownCommand,
386 InvalidSenderAddress,
387 InvalidRecipientAddress,
388 SyntaxError { syntax: &'static str },
389 InvalidParameter { param: &'static str },
390 UnsupportedParameter { param: String },
391 ResponseTooLong,
392 InvalidResponse { code: u16 },
393}
394
395pub(crate) const LF: u8 = b'\n';
396pub(crate) const SP: u8 = b' ';
397
398pub trait IntoString: Sized {
399 fn into_string(self) -> String;
400}
401
402impl IntoString for Vec<u8> {
403 fn into_string(self) -> String {
404 String::from_utf8(self)
405 .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned())
406 }
407}
408
409impl<T: Default> Default for MailFrom<T> {
410 fn default() -> Self {
411 Self {
412 address: Default::default(),
413 flags: Default::default(),
414 size: Default::default(),
415 trans_id: Default::default(),
416 by: Default::default(),
417 env_id: Default::default(),
418 solicit: Default::default(),
419 mtrk: Default::default(),
420 auth: Default::default(),
421 hold_for: Default::default(),
422 hold_until: Default::default(),
423 mt_priority: Default::default(),
424 }
425 }
426}
427
428impl<T: Default> Default for RcptTo<T> {
429 fn default() -> Self {
430 Self {
431 address: Default::default(),
432 orcpt: Default::default(),
433 rrvs: Default::default(),
434 flags: Default::default(),
435 }
436 }
437}
438
439impl<T: Display> AsRef<EhloResponse<T>> for EhloResponse<T> {
440 fn as_ref(&self) -> &EhloResponse<T> {
441 self
442 }
443}
444
445impl Display for Error {
446 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
447 match self {
448 Error::NeedsMoreData { bytes_left } => {
449 write!(f, "Needs more data: {} bytes left", bytes_left)
450 }
451 Error::UnknownCommand => write!(f, "Unknown command"),
452 Error::InvalidSenderAddress => write!(f, "Invalid sender address"),
453 Error::InvalidRecipientAddress => write!(f, "Invalid recipient address"),
454 Error::SyntaxError { syntax } => write!(f, "Syntax error: {}", syntax),
455 Error::InvalidParameter { param } => write!(f, "Invalid parameter: {}", param),
456 Error::UnsupportedParameter { param } => {
457 write!(f, "Unsupported parameter: {}", param)
458 }
459 Error::ResponseTooLong => write!(f, "Response too long"),
460 Error::InvalidResponse { code } => write!(f, "Invalid response: {}", code),
461 }
462 }
463}
464
465impl std::error::Error for Error {}