Author: Kevin Schoon [me@kevinschoon.com]
Hash: 976e385a35d006eb831d393fce0828161b772b48
Timestamp: Mon, 12 Aug 2024 14:59:53 +0000 (2 months ago)

+570 -2 +/-8 browse
partially implement EXPN and VRFY commands
1diff --git a/Cargo.lock b/Cargo.lock
2index f80ce72..1e7864d 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -796,6 +796,7 @@ dependencies = [
6 "bytes",
7 "futures",
8 "mail-parser",
9+ "md5",
10 "melib",
11 "smtp-proto",
12 "thiserror",
13 @@ -818,6 +819,12 @@ dependencies = [
14 ]
15
16 [[package]]
17+ name = "md5"
18+ version = "0.7.0"
19+ source = "registry+https://github.com/rust-lang/crates.io-index"
20+ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
21+
22+ [[package]]
23 name = "melib"
24 version = "0.8.6"
25 source = "registry+https://github.com/rust-lang/crates.io-index"
26 diff --git a/maitred/Cargo.toml b/maitred/Cargo.toml
27index 81236de..9626221 100644
28--- a/maitred/Cargo.toml
29+++ b/maitred/Cargo.toml
30 @@ -7,6 +7,7 @@ edition = "2021"
31 bytes = "1.6.1"
32 futures = "0.3.30"
33 mail-parser = { version = "0.9.3", features = ["serde", "serde_support"] }
34+ md5 = "0.7.0"
35 melib = { version = "0.8.6", default-features = false, features = ["base64", "smtp"] }
36 smtp-proto = { version = "0.1.5", features = ["serde", "serde_support"] }
37 thiserror = "1.0.63"
38 diff --git a/maitred/src/addresses.rs b/maitred/src/addresses.rs
39new file mode 100644
40index 0000000..4728649
41--- /dev/null
42+++ b/maitred/src/addresses.rs
43 @@ -0,0 +1,16 @@
44+ use std::fmt::Display;
45+
46+ use melib::Address;
47+
48+ /// Array of resolved e-mail addresses that are associated with a mailing list
49+ #[derive(Debug)]
50+ pub struct Addresses(pub Vec<Address>);
51+
52+ impl Display for Addresses {
53+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54+ let addresses: Vec<String> = self.0.iter().map(|address| address.to_string()).collect();
55+ write!(f, "{}", addresses.join("\n"))
56+ }
57+ }
58+
59+
60 diff --git a/maitred/src/expansion.rs b/maitred/src/expansion.rs
61new file mode 100644
62index 0000000..72e1ddc
63--- /dev/null
64+++ b/maitred/src/expansion.rs
65 @@ -0,0 +1,40 @@
66+ use std::{fmt::Display, result::Result as StdResult};
67+
68+ use crate::addresses::Addresses;
69+
70+ pub type Result = StdResult<Addresses, Error>;
71+
72+ /// An error encountered while expanding a mail address
73+ #[derive(Debug, thiserror::Error)]
74+ pub enum Error {
75+ /// Indicates an unspecified error that occurred during expansion
76+ #[error("Internal Server Error: {0}")]
77+ Server(String),
78+ /// Indicates that no group exists with the specified name
79+ #[error("Group Not Found: {0}")]
80+ NotFound(String),
81+ }
82+
83+ /// Expands a string representing a mailing list to an array of the associated
84+ /// addresses within the list if it exists. NOTE: That this function should
85+ /// only be called with proper authentication otherwise it could be used to
86+ /// harvest e-mail addresses.
87+ pub trait Expansion {
88+ /// Expand the group into an array of members
89+ fn expand(&self, name: &str) -> Result;
90+ }
91+
92+ /// Wrapper type implementing the Expansion trait
93+ pub struct Func<F>(pub F)
94+ where
95+ F: Fn(&str) -> Result;
96+
97+ impl<F> Expansion for Func<F>
98+ where
99+ F: Fn(&str) -> Result,
100+ {
101+ fn expand(&self, name: &str) -> Result {
102+ let f = &self.0;
103+ f(name)
104+ }
105+ }
106 diff --git a/maitred/src/lib.rs b/maitred/src/lib.rs
107index a010ec5..e523b2c 100644
108--- a/maitred/src/lib.rs
109+++ b/maitred/src/lib.rs
110 @@ -1,8 +1,11 @@
111+ mod addresses;
112 mod error;
113+ mod expansion;
114 mod pipeline;
115 mod server;
116 mod session;
117 mod transport;
118+ mod verify;
119
120 use smtp_proto::{Request, Response as SmtpResponse};
121
122 diff --git a/maitred/src/session.rs b/maitred/src/session.rs
123index d23d57d..4ef0078 100644
124--- a/maitred/src/session.rs
125+++ b/maitred/src/session.rs
126 @@ -6,7 +6,9 @@ use melib::Address;
127 use smtp_proto::{EhloResponse, Request, Response as SmtpResponse};
128 use url::Host;
129
130+ use crate::expansion::Expansion;
131 use crate::transport::Response;
132+ use crate::verify::Verify;
133 use crate::{smtp_err, smtp_ok, smtp_response};
134
135 /// Result generated as part of an SMTP session, an Err indicates a session
136 @@ -53,6 +55,8 @@ pub(crate) struct Session {
137 maximum_size: u64,
138 capabilities: u32,
139 help_banner: String,
140+ list_expansion: Option<Box<dyn Expansion>>,
141+ verification: Option<Box<dyn Verify>>,
142 }
143
144 impl Session {
145 @@ -76,6 +80,19 @@ impl Session {
146 self
147 }
148
149+ pub fn list_expansion<T>(mut self, expansion: T) -> Self
150+ where
151+ T: crate::expansion::Expansion + 'static,
152+ {
153+ self.list_expansion = Some(Box::new(expansion));
154+ self
155+ }
156+
157+ pub fn verification(mut self, verification: Box<dyn Verify>) -> Self {
158+ self.verification = Some(verification);
159+ self
160+ }
161+
162 pub fn reset(&mut self) {
163 self.body = None;
164 self.mail_from = None;
165 @@ -236,8 +253,56 @@ impl Session {
166 self.check_initialized()?;
167 smtp_ok!(250, 0, 0, 0, "OK".to_string())
168 }
169- Request::Vrfy { value } => todo!(),
170- Request::Expn { value } => todo!(),
171+ Request::Vrfy { value } => {
172+ if let Some(verifier) = &self.verification {
173+ let address = Address::try_from(value.as_str()).map_err(|e| {
174+ smtp_response!(500, 0, 0, 0, format!("Cannot parse: {} {}", value, e))
175+ })?;
176+ match verifier.verify(&address) {
177+ Ok(_) => {
178+ smtp_ok!(200, 0, 0, 0, "Ok".to_string())
179+ }
180+ Err(e) => match e {
181+ crate::verify::Error::Server(e) => {
182+ smtp_err!(500, 0, 0, 0, e.to_string())
183+ }
184+ crate::verify::Error::NotFound(e) => {
185+ smtp_err!(500, 0, 0, 0, e.to_string())
186+ }
187+ crate::verify::Error::Ambiguous(alternatives) => {
188+ smtp_err!(500, 0, 0, 0, alternatives.to_string())
189+ }
190+ },
191+ }
192+ } else {
193+ smtp_err!(500, 0, 0, 0, "No such address")
194+ }
195+ }
196+ Request::Expn { value } => {
197+ if let Some(expn) = &self.list_expansion {
198+ match expn.expand(value) {
199+ Ok(addresses) => {
200+ smtp_ok!(250, 0, 0, 0, addresses.to_string())
201+ }
202+ Err(err) => match err {
203+ crate::expansion::Error::Server(message) => {
204+ smtp_err!(
205+ 500,
206+ 0,
207+ 0,
208+ 0,
209+ format!("Internal mailing list error: {}", message)
210+ )
211+ }
212+ crate::expansion::Error::NotFound(name) => {
213+ smtp_err!(500, 0, 0, 0, format!("No such mailing list: {}", name))
214+ }
215+ },
216+ }
217+ } else {
218+ smtp_err!(500, 0, 0, 0, "Server does not support EXPN")
219+ }
220+ }
221 Request::Help { value } => {
222 self.check_initialized()?;
223 if value.is_empty() {
224 diff --git a/maitred/src/verify.rs b/maitred/src/verify.rs
225new file mode 100644
226index 0000000..a1bf270
227--- /dev/null
228+++ b/maitred/src/verify.rs
229 @@ -0,0 +1,41 @@
230+ use std::{fmt::Display, result::Result as StdResult};
231+
232+ use crate::addresses::Addresses;
233+ use melib::Address;
234+
235+ pub type Result = StdResult<(), Error>;
236+
237+ /// An error encountered while verifying an e-mail address
238+ #[derive(Debug, thiserror::Error)]
239+ pub enum Error {
240+ /// Indicates an unspecified error that occurred during expansion
241+ #[error("Internal Server Error: {0}")]
242+ Server(String),
243+ /// Indicates that no group exists with the specified name
244+ #[error("Group Not Found: {0}")]
245+ NotFound(String),
246+ /// Indicates that the input as ambigious and multiple addresses are
247+ /// associated with the string.
248+ #[error("Name is Ambiguous: {0}")]
249+ Ambiguous(Addresses),
250+ }
251+
252+ pub trait Verify {
253+ /// Verify the e-mail address on the server
254+ fn verify(&self, address: &Address) -> Result;
255+ }
256+
257+ /// Wrapper type implementing the Verify trait
258+ pub struct Func<F>(pub F)
259+ where
260+ F: Fn(&Address) -> Result;
261+
262+ impl<F> Verify for Func<F>
263+ where
264+ F: Fn(&Address) -> Result,
265+ {
266+ fn verify(&self, address: &Address) -> Result {
267+ let f = &self.0;
268+ f(address)
269+ }
270+ }
271 diff --git a/rfcs/rfc2033.txt b/rfcs/rfc2033.txt
272new file mode 100644
273index 0000000..a47433e
274--- /dev/null
275+++ b/rfcs/rfc2033.txt
276 @@ -0,0 +1,395 @@
277+
278+
279+
280+
281+
282+
283+ Network Working Group J. Myers
284+ Request for Comments: 2033 Carnegie Mellon
285+ Category: Informational October 1996
286+
287+
288+ Local Mail Transfer Protocol
289+
290+ Status of this Memo
291+
292+ This memo provides information for the Internet community. This memo
293+ does not specify an Internet standard of any kind. Distribution of
294+ this memo is unlimited.
295+
296+ 1. Abstract
297+
298+ SMTP [SMTP] [HOST-REQ] and its service extensions [ESMTP] provide a
299+ mechanism for transferring mail reliably and efficiently. The design
300+ of the SMTP protocol effectively requires the server to manage a mail
301+ delivery queue.
302+
303+ In some limited circumstances, outside the area of mail exchange
304+ between independent hosts on public networks, it is desirable to
305+ implement a system where a mail receiver does not manage a queue.
306+ This document describes the LMTP protocol for transporting mail into
307+ such systems.
308+
309+ Although LMTP is an alternative protocol to ESMTP, it uses (with a
310+ few changes) the syntax and semantics of ESMTP. This design permits
311+ LMTP to utilize the extensions defined for ESMTP. LMTP should be
312+ used only by specific prior arrangement and configuration, and it
313+ MUST NOT be used on TCP port 25.
314+
315+ Table of Contents
316+
317+ 1. Abstract ................................................ 1
318+ 2. Conventions Used in this Document ....................... 2
319+ 3. Introduction and Overview ............................... 2
320+ 4. The LMTP protocol ....................................... 3
321+ 4.1. The LHLO, HELO and EHLO commands ........................ 4
322+ 4.2. The DATA command ........................................ 4
323+ 4.3. The BDAT command ........................................ 5
324+ 5. Implementation requirements ............................. 6
325+ 6. Acknowledgments ......................................... 6
326+ 7. References .............................................. 7
327+ 8. Security Considerations ................................. 7
328+ 9. Author's Address ........................................ 7
329+
330+
331+
332+
333+
334+ Myers Informational [Page 1]
335+
336+ RFC 2033 LMTP October 1996
337+
338+
339+ 2. Conventions Used in this Document
340+
341+ In examples, "C:" and "S:" indicate lines sent by the client and
342+ server respectively.
343+
344+ 3. Introduction and Overview
345+
346+ The design of the SMTP protocol effectively requires the server to
347+ manage a mail delivery queue. This is because a single mail
348+ transaction may specify multiple recipients and the final "." of the
349+ DATA command may return only one reply code, to indicate the status
350+ of the entire transaction. If, for example, a server is given a
351+ transaction for two recipients, delivery to the first succeeds, and
352+ delivery to the second encounters a temporary failure condition,
353+ there is no mechanism to inform the client of the situation. The
354+ server must queue the message and later attempt to deliver it to the
355+ second recipient.
356+
357+ This queuing requirement is beneficial in the situation for which
358+ SMTP was originally designed: store-and-forward relay of mail between
359+ networked hosts. In some limited situations, it is desirable to have
360+ a server which does not manage a queue, instead relying on the client
361+ to perform queue management. As an example, consider a hypothetical
362+ host with a mail system designed as follows:
363+
364+
365+
366+ TCP port 25 +-----------------+
367+ ---------------------->| | #########
368+ | Queue |<># Mail #
369+ TCP port 25 | Manager | # Queue #
370+ <----------------------| | #########
371+ +-----------------+
372+ Local * ^ Local * Local
373+ IPC * | IPC * IPC
374+ * | *
375+ * | *
376+ * | *
377+ V | V
378+ Non-SMTP +----------+ +----------+
379+ Protocol | Gateway | | Local | #########
380+ <==============>| Delivery | | Delivery |>># Mail #
381+ | Agent | | Agent | # Spool #
382+ +----------+ +----------+ #########
383+
384+
385+ The host's mail system has three independent, communicating
386+ subsystems. The first is a queue manager, which acts as a
387+
388+
389+
390+ Myers Informational [Page 2]
391+
392+ RFC 2033 LMTP October 1996
393+
394+
395+ traditional SMTP agent, transferring messages to and from other hosts
396+ over TCP and managing a mail queue in persistent storage. The other
397+ two are agents which handle delivery for addresses in domains for
398+ which the host takes responsibility. One agent performs gatewaying
399+ to and from some other mail system. The other agent delivers the
400+ message into a persistent mail spool.
401+
402+ It would be desirable to use SMTP over a local inter-process
403+ communication channel to transfer messages from the queue manager to
404+ the delivery agents. It would, however, significantly increase the
405+ complexity of the delivery agents to require them to manage their own
406+ mail queues.
407+
408+ The common practice of invoking a delivery agent with the envelope
409+ address(es) as command-line arguments, then having the delivery agent
410+ communicate status with an exit code has three serious problems: the
411+ agent can only return one exit code to be applied to all recipients,
412+ it is difficult to extend the interface to deal with ESMTP extensions
413+ such as DSN [DSN] and ENHANCEDSTATUSCODES [ENHANCEDSTATUSCODES], and
414+ exits performed by system libraries due to temporary conditions
415+ frequently get interpreted as permanent errors.
416+
417+ The LMTP protocol causes the server to return, after the final "." of
418+ the DATA command, one reply for each recipient. Therefore, if the
419+ queue manager is configured to use LMTP instead of SMTP when
420+ transferring messages to the delivery agents, then the delivery
421+ agents may attempt delivery to each recipient after the final "." and
422+ individually report the status for each recipient. Connections which
423+ should use the LMTP protocol are drawn in the diagram above using
424+ asterisks.
425+
426+ Note that it is not beneficial to use the LMTP protocol when
427+ transferring messages to the queue manager, either from the network
428+ or from a delivery agent. The queue manager does implement a mail
429+ queue, so it may store the message and take responsibility for later
430+ delivering it.
431+
432+ 4. The LMTP protocol
433+
434+ The LMTP protocol is identical to the SMTP protocol SMTP [SMTP]
435+ [HOST-REQ] with its service extensions [ESMTP], except as modified by
436+ this document.
437+
438+ A "successful" RCPT command is defined as an RCPT command which
439+ returns a Positive Completion reply code.
440+
441+ A "Positive Completion reply code" is defined in Appendix E of STD
442+ 10, RFC 821 [SMTP] as a reply code which "2" as the first digit.
443+
444+
445+
446+ Myers Informational [Page 3]
447+
448+ RFC 2033 LMTP October 1996
449+
450+
451+ 4.1. The LHLO, HELO and EHLO commands
452+
453+ The HELO and EHLO commands of ESMTP are replaced by the LHLO command.
454+ This permits a misconfiguration where both parties are not using the
455+ same protocol to be detected.
456+
457+ The LHLO command has identical semantics to the EHLO command of ESMTP
458+ [ESMTP].
459+
460+ The HELO and EHLO commands of ESMTP are not present in LMTP. A LMTP
461+ server MUST NOT return a Postive Completion reply code to these
462+ commands. The 500 reply code is recommended.
463+
464+ 4.2. The DATA command
465+
466+ In the LMTP protocol, there is one additional restriction placed on
467+ the DATA command, and one change to how replies to the final "." are
468+ sent.
469+
470+ The additional restriction is that when there have been no successful
471+ RCPT commands in the mail transaction, the DATA command MUST fail
472+ with a 503 reply code.
473+
474+ The change is that after the final ".", the server returns one reply
475+ for each previously successful RCPT command in the mail transaction,
476+ in the order that the RCPT commands were issued. Even if there were
477+ multiple successful RCPT commands giving the same forward-path, there
478+ must be one reply for each successful RCPT command.
479+
480+ When one of these replies to the final "." is a Positive Completion
481+ reply, the server is accepting responsibility for delivering or
482+ relying the message to the corresponding recipient. It must take
483+ this responsibility seriously, i.e., it MUST NOT lose the message for
484+ frivolous reasons, e.g., because the host later crashes or because of
485+ a predictable resource shortage.
486+
487+ A multiline reply is still considered a single reply and corresponds
488+ to a single RCPT command.
489+
490+ EXAMPLE:
491+
492+ S: 220 foo.edu LMTP server ready
493+ C: LHLO foo.edu
494+ S: 250-foo.edu
495+ S: 250-PIPELINING
496+ S: 250 SIZE
497+ C: MAIL FROM:<chris@bar.com>
498+ S: 250 OK
499+
500+
501+
502+ Myers Informational [Page 4]
503+
504+ RFC 2033 LMTP October 1996
505+
506+
507+ C: RCPT TO:<pat@foo.edu>
508+ S: 250 OK
509+ C: RCPT TO:<jones@foo.edu>
510+ S: 550 No such user here
511+ C: RCPT TO:<green@foo.edu>
512+ S: 250 OK
513+ C: DATA
514+ S: 354 Start mail input; end with <CRLF>.<CRLF>
515+ C: Blah blah blah...
516+ C: ...etc. etc. etc.
517+ C: .
518+ S: 250 OK
519+ S: 452 <green@foo.edu> is temporarily over quota
520+ C: QUIT
521+ S: 221 foo.edu closing connection
522+
523+
524+ NOTE: in the above example, the domain names of both the client and
525+ server are identical. This is because in the example the client and
526+ server are different subsystems of the same mail domain.
527+
528+ 4.3. The BDAT command
529+
530+ If the server supports the ESMTP CHUNKING extension [BINARYMIME], a
531+ BDAT command containing the LAST parameter returns one reply for each
532+ previously successful RCPT command in the mail transaction, in the
533+ order that the RCPT commands were issued. Even if there were
534+ multiple successful RCPT commands giving the same forward-path, there
535+ must be one reply for each successful RCPT command. If there were no
536+ previously successful RCPT commands in the mail transaction, then the
537+ BDAT LAST command returns zero replies.
538+
539+ When one of these replies to the BDAT LAST command is a Positive
540+ Completion reply, the server is accepting responsibility for
541+ delivering or relaying the message to the corresponding recipient.
542+ It must take this responsibility seriously, i.e., it MUST NOT lose
543+ the message for frivolous reasons, e.g., because the host later
544+ crashes or because of a predictable resource shortage.
545+
546+ A multiline reply is still considered a single reply and corresponds
547+ to a single RCPT command.
548+
549+ The behavior of BDAT commands without the LAST parameter is not
550+ changed; they still return exactly one reply.
551+
552+
553+
554+
555+
556+
557+
558+ Myers Informational [Page 5]
559+
560+ RFC 2033 LMTP October 1996
561+
562+
563+ 5. Implementation requirements
564+
565+ As LMTP is a different protocol than SMTP, it MUST NOT be used on the
566+ TCP service port 25.
567+
568+ A server implementation MUST implement the PIPELINING [PIPELINING]
569+ and ENHANCEDSTATUSCODES [ENHANCEDSTATUSCODES] ESMTP extensions. A
570+ server implementation SHOULD implement the 8BITMIME [8BITMIME]
571+ extension.
572+
573+ Use of LMTP can aggravate the situation described in [DUP-MSGS]. To
574+ avoid this synchronization problem, the following requirements are
575+ made of implementations:
576+
577+ A server implementation which is capable of quickly accepting
578+ responsibility for delivering or relaying a message to multiple
579+ recipients and which is capable of sending any necessary notification
580+ messages SHOULD NOT implement the LMTP protocol.
581+
582+ The LMTP protocol SHOULD NOT be used over wide area networks.
583+
584+ The server SHOULD send each reply as soon as possible. If it is
585+ going to spend a nontrivial amount of time handling delivery for the
586+ next recipient, it SHOULD flush any outgoing LMTP buffer, so the
587+ reply may be quickly received by the client.
588+
589+ The client SHOULD process the replies as they come in, instead of
590+ waiting for all of the replies to arrive before processing any of
591+ them. If the connection closes after replies for some, but not all,
592+ recipients have arrived, the client MUST process the replies that
593+ arrived and treat the rest as temporary failures.
594+
595+ 6. Acknowledgments
596+
597+ This work is a refinement of the MULT extension, which was invented
598+ by Jeff Michaud and was used in implementing gateways to the Mail-11
599+ mail system.
600+
601+ Many thanks to Matt Thomas for assisting me in understanding the
602+ semantics of the Mail-11 MULT extension.
603+
604+
605+
606+
607+
608+
609+
610+
611+
612+
613+
614+ Myers Informational [Page 6]
615+
616+ RFC 2033 LMTP October 1996
617+
618+
619+ 7. References
620+
621+ [8BITMIME] Klensin, J., et. al, "SMTP Service Extension for 8bit-MIME
622+ transport", RFC 1652, July 1994.
623+
624+ [BINARYMIME] Vaudreuil, G., "SMTP Service Extensions for Transmission
625+ of Large and Binary MIME Messages", RFC 1830, August 1995.
626+
627+ [DSN] Moore, K., Vaudreuil, G., "An Extensible Message Format for
628+ Delivery Status Notifications", RFC 1894, January 1996.
629+
630+ [DUP-MSGS] Partridge, C., "Duplicate messages and SMTP", RFC 1047,
631+ February 1988.
632+
633+ [ENHANCEDSTATUSCODES] Freed, N., "SMTP Service Extension for
634+ Returning Enhanced Error Codes", RFC 2034, October 1996.
635+
636+ [ESMTP] Rose, M., Stefferud, E., Crocker, C., Klensin, J., Freed, N.,
637+ "SMTP Service Extensions", RFC 1869, November 1995.
638+
639+ [HOST-REQ] Braden, R., "Requirements for Internet hosts - application
640+ and support", STD 3, RFC 1123 section 5, October 1989.
641+
642+ [PIPELINING] Freed, N., Cargille, A, "SMTP Service Extension for
643+ Command Pipelining", RFC 1854, October 1995.
644+
645+ [SMTP] Postel, J., "Simple Mail Transfer Protocol", STD 10, RFC 821,
646+ August 1982.
647+
648+
649+ There are no known security issues with the issues in this memo.
650+
651+ 9. Author's Address
652+
653+ John G. Myers
654+ Carnegie-Mellon University
655+ 5000 Forbes Ave.
656+ Pittsburgh PA, 15213-3890
657+
658+ EMail: jgm+@cmu.edu
659+
660+
661+
662+
663+
664+
665+
666+
667+
668+
669+
670+ Myers Informational [Page 7]
671+