Author: Kevin Schoon [me@kevinschoon.com]
Hash: 5ef63f81a55b53f441e3fdb713d9073baf5a70dc
Timestamp: Sun, 04 Aug 2024 13:05:48 +0000 (2 months ago)

+493 -57 +/-4 browse
implement 8BITMIME
1diff --git a/README.md b/README.md
2index 798d858..d48e59b 100644
3--- a/README.md
4+++ b/README.md
5 @@ -48,9 +48,9 @@ for _absolutely nothing_ that is important.
6
7 | Name | Status | RFC |
8 |-----------------------|----------|-------------------------------|
9- | SIZE | TODO | [RFC1870](rfcs/rfc1870.txt) |
10- | PIPELINING | TODO | [RFC2920](rfcs/rfc2920.txt) |
11- | 8BITMIME | TODO | [RFC6152](rfcs/rfc6152.txt) |
12+ | SIZE | ✅ | [RFC1870](rfcs/rfc1870.txt) |
13+ | PIPELINING | ✅ | [RFC2920](rfcs/rfc2920.txt) |
14+ | 8BITMIME | ✅ | [RFC6152](rfcs/rfc6152.txt) |
15 | ENHANCED STATUS CODES | TODO | [RFC2920](rfcs/rfc3463.txt) |
16 | SMTPUTF8 | TODO | [RFC6531](rfcs/rfc6531.txt) |
17 | CHUNKING | TODO | [RFC3030](rfcs/rfc3030.txt) |
18 diff --git a/maitred/src/server.rs b/maitred/src/server.rs
19index 303490b..44e0b85 100644
20--- a/maitred/src/server.rs
21+++ b/maitred/src/server.rs
22 @@ -9,7 +9,7 @@ use tokio_util::codec::Framed;
23
24 use crate::error::Error;
25 use crate::pipeline::Pipeline;
26- use crate::session::{Options as SessionOptions, Session};
27+ use crate::session::Session;
28 use crate::transport::{Response, Transport};
29
30 const DEFAULT_LISTEN_ADDR: &str = "127.0.0.1:2525";
31 @@ -36,8 +36,10 @@ const DEFAULT_MAXIMUM_SIZE: u64 = 5_000_000;
32 // 250-SMTPUTF8
33 // 250 CHUNKING
34
35- const DEFAULT_CAPABILITIES: u32 =
36- smtp_proto::EXT_SIZE | smtp_proto::EXT_ENHANCED_STATUS_CODES | smtp_proto::EXT_PIPELINING;
37+ pub const DEFAULT_CAPABILITIES: u32 = smtp_proto::EXT_SIZE
38+ | smtp_proto::EXT_ENHANCED_STATUS_CODES
39+ | smtp_proto::EXT_PIPELINING
40+ | smtp_proto::EXT_8BIT_MIME;
41
42 /// Apply pipelining if running in extended mode and configured to support it
43 struct ConditionalPipeline<'a> {
44 @@ -65,42 +67,6 @@ impl ConditionalPipeline<'_> {
45 }
46 }
47
48- #[derive(Clone)]
49- struct Configuration {
50- pub address: String,
51- pub hostname: String,
52- pub greeting: String,
53- pub global_timeout: Duration,
54- pub help_banner: String,
55- pub maximum_size: u64,
56- capabilities: u32,
57- }
58-
59- impl Configuration {
60- pub fn session_opts(&self) -> SessionOptions {
61- SessionOptions {
62- hostname: self.hostname.clone(),
63- capabilities: self.capabilities,
64- help_banner: self.help_banner.clone(),
65- maximum_size: self.maximum_size,
66- }
67- }
68- }
69-
70- impl Default for Configuration {
71- fn default() -> Self {
72- Configuration {
73- address: DEFAULT_LISTEN_ADDR.to_string(),
74- hostname: String::default(),
75- greeting: DEFAULT_GREETING.to_string(),
76- global_timeout: Duration::from_secs(DEFAULT_GLOBAL_TIMEOUT_SECS),
77- help_banner: DEFAULT_HELP_BANNER.to_string(),
78- capabilities: DEFAULT_CAPABILITIES,
79- maximum_size: DEFAULT_MAXIMUM_SIZE,
80- }
81- }
82- }
83-
84 pub struct Server {
85 address: String,
86 hostname: String,
87 diff --git a/maitred/src/session.rs b/maitred/src/session.rs
88index 36e545e..d23d57d 100644
89--- a/maitred/src/session.rs
90+++ b/maitred/src/session.rs
91 @@ -34,17 +34,6 @@ pub fn timeout(message: &str) -> Response<String> {
92 smtp_response!(421, 4, 4, 2, format!("Timeout exceeded: {}", message))
93 }
94
95- /// Runtime options that influence server behavior
96- #[derive(Default, Clone)]
97- pub(crate) struct Options {
98- pub hostname: String,
99- /// Generic banner to show when the help command is sent without any
100- /// arguments.
101- pub help_banner: String,
102- pub capabilities: u32,
103- pub maximum_size: u64,
104- }
105-
106 /// Stateful connection that coresponds to a single SMTP session
107 #[derive(Default)]
108 pub(crate) struct Session {
109 @@ -58,7 +47,7 @@ pub(crate) struct Session {
110 /// If an active data transfer is taking place
111 data_transfer: Option<DataTransfer>,
112 initialized: Option<Mode>,
113- options: Options,
114+
115 // session options
116 our_hostname: String,
117 maximum_size: u64,
118 @@ -105,7 +94,10 @@ impl Session {
119
120 /// check if the capability is supported by the session
121 pub fn has_capability(&self, capability: u32) -> bool {
122- self.capabilities & capability != 0
123+ self.initialized
124+ .as_ref()
125+ .is_some_and(|mode| matches!(mode, Mode::Extended))
126+ && self.capabilities & capability != 0
127 }
128
129 /// ensure that the session has been initialized otherwise return an error
130 @@ -123,8 +115,17 @@ impl Session {
131 }
132
133 /// checks if 8BITMIME is supported
134- fn non_ascii_ok(&self) -> bool {
135- false
136+ fn check_body(&self, body: &[u8]) -> StdResult<(), Response<String>> {
137+ if !self.has_capability(smtp_proto::EXT_8BIT_MIME) && !body.is_ascii() {
138+ return Err(smtp_response!(
139+ 500,
140+ 0,
141+ 0,
142+ 0,
143+ "Non ascii characters found in message body"
144+ ));
145+ }
146+ Ok(())
147 }
148
149 /// Statefully process the SMTP command with optional data payload, any
150 @@ -141,6 +142,7 @@ impl Session {
151 self.hostname = Some(Host::parse(host).map_err(|e| {
152 Response::General(SmtpResponse::new(500, 0, 0, 0, e.to_string()))
153 })?);
154+ self.reset();
155 self.initialized = Some(Mode::Extended);
156 let mut resp = EhloResponse::new(format!("Hello {}", host));
157 resp.capabilities = self.capabilities;
158 @@ -150,6 +152,7 @@ impl Session {
159 Request::Lhlo { host } => {
160 self.hostname =
161 Some(Host::parse(host).map_err(|e| smtp_response!(500, 0, 0, 0, e))?);
162+ self.reset();
163 self.initialized = Some(Mode::Legacy);
164 smtp_ok!(250, 0, 0, 0, format!("Hello {}", host))
165 }
166 @@ -157,6 +160,7 @@ impl Session {
167 self.hostname = Some(
168 Host::parse(host).map_err(|e| smtp_response!(500, 0, 0, 0, e.to_string()))?,
169 );
170+ self.reset();
171 self.initialized = Some(Mode::Legacy);
172 smtp_ok!(250, 0, 0, 0, format!("Hello {}", host))
173 }
174 @@ -201,6 +205,7 @@ impl Session {
175 .as_ref()
176 .expect("data returned without a payload")
177 .to_vec();
178+ self.check_body(&message_payload)?;
179 let parser = MessageParser::new();
180 let response = match parser.parse(&message_payload) {
181 Some(_) => smtp_ok!(250, 0, 0, 0, "OK"),
182 @@ -263,6 +268,7 @@ impl Session {
183 .as_ref()
184 .expect("data returned without a payload")
185 .to_vec();
186+ self.check_body(&message_payload)?;
187 let parser = MessageParser::new();
188 let response = match parser.parse(&message_payload) {
189 Some(_) => smtp_ok!(250, 0, 0, 0, "OK".to_string()),
190 @@ -400,6 +406,75 @@ mod test {
191 }
192
193 #[test]
194+ fn test_non_ascii_characters() {
195+ let mut expected_ehlo_response = EhloResponse::new(String::from("Hello example.org"));
196+ expected_ehlo_response.capabilities = crate::server::DEFAULT_CAPABILITIES;
197+ let requests = &[
198+ TestCase {
199+ request: Request::Helo {
200+ host: EXAMPLE_HOSTNAME.to_string(),
201+ },
202+ payload: None,
203+ expected: smtp_ok!(250, 0, 0, 0, String::from("Hello example.org")),
204+ },
205+ TestCase {
206+ request: Request::Data {},
207+ payload: None,
208+ expected: smtp_ok!(
209+ 354,
210+ 0,
211+ 0,
212+ 0,
213+ "Reading data input, end the message with <CRLF>.<CRLF>"
214+ ),
215+ },
216+ TestCase {
217+ request: Request::Data {},
218+ payload: Some(Bytes::from_static(
219+ r#"Subject: Hello World
220+ 😍😍😍
221+ "#
222+ .as_bytes(),
223+ )),
224+ expected: smtp_err!(500, 0, 0, 0, "Non ascii characters found in message body"),
225+ },
226+ // upgrade the connection to extended mode
227+ TestCase {
228+ request: Request::Ehlo {
229+ host: EXAMPLE_HOSTNAME.to_string(),
230+ },
231+ payload: None,
232+ expected: Ok(Response::Ehlo(expected_ehlo_response)),
233+ },
234+ TestCase {
235+ request: Request::Data {},
236+ payload: None,
237+ expected: smtp_ok!(
238+ 354,
239+ 0,
240+ 0,
241+ 0,
242+ "Reading data input, end the message with <CRLF>.<CRLF>"
243+ ),
244+ },
245+ TestCase {
246+ request: Request::Data {},
247+ payload: Some(Bytes::from_static(
248+ r#"Subject: Hello World
249+ 😍😍😍
250+ "#
251+ .as_bytes(),
252+ )),
253+ expected: smtp_ok!(250, 0, 0, 0, "OK"),
254+ },
255+ ];
256+ let mut session = Session::default()
257+ .our_hostname(EXAMPLE_HOSTNAME)
258+ .capabilities(crate::server::DEFAULT_CAPABILITIES);
259+ process_all(&mut session, requests);
260+ }
261+
262+ #[test]
263 fn test_email_with_body() {
264 let requests = &[
265 TestCase {
266 diff --git a/rfcs/rfc6152.txt b/rfcs/rfc6152.txt
267new file mode 100644
268index 0000000..a2bfa59
269--- /dev/null
270+++ b/rfcs/rfc6152.txt
271 @@ -0,0 +1,395 @@
272+
273+
274+
275+
276+
277+
278+ Internet Engineering Task Force (IETF) J. Klensin
279+ Request for Comments: 6152
280+ STD: 71 N. Freed
281+ Obsoletes: 1652 Oracle
282+ Category: Standards Track M. Rose
283+ ISSN: 2070-1721 Dover Beach Consulting, Inc.
284+ D. Crocker, Ed.
285+ Brandenburg InternetWorking
286+ March 2011
287+
288+
289+ SMTP Service Extension for 8-bit MIME Transport
290+
291+ Abstract
292+
293+ This memo defines an extension to the SMTP service whereby an SMTP
294+ content body consisting of text containing octets outside of the
295+ US-ASCII octet range (hex 00-7F) may be relayed using SMTP.
296+
297+ Status of This Memo
298+
299+ This is an Internet Standards Track document.
300+
301+ This document is a product of the Internet Engineering Task Force
302+ (IETF). It represents the consensus of the IETF community. It has
303+ received public review and has been approved for publication by the
304+ Internet Engineering Steering Group (IESG). Further information on
305+ Internet Standards is available in Section 2 of RFC 5741.
306+
307+ Information about the current status of this document, any errata,
308+ and how to provide feedback on it may be obtained at
309+ http://www.rfc-editor.org/info/rfc6152.
310+
311+ Copyright Notice
312+
313+ Copyright (c) 2011 IETF Trust and the persons identified as the
314+ document authors. All rights reserved.
315+
316+ This document is subject to BCP 78 and the IETF Trust's Legal
317+ Provisions Relating to IETF Documents
318+ (http://trustee.ietf.org/license-info) in effect on the date of
319+ publication of this document. Please review these documents
320+ carefully, as they describe your rights and restrictions with respect
321+ to this document. Code Components extracted from this document must
322+ include Simplified BSD License text as described in Section 4.e of
323+ the Trust Legal Provisions and are provided without warranty as
324+ described in the Simplified BSD License.
325+
326+
327+
328+
329+ Klensin, et al. Standards Track [Page 1]
330+
331+ RFC 6152 SMTP Extension for 8-bit MIME March 2011
332+
333+
334+ 1. Introduction
335+
336+ Although SMTP is widely and robustly deployed, various extensions
337+ have been requested by parts of the Internet community. In
338+ particular, a significant portion of the Internet community wishes to
339+ exchange messages in which the content body consists of a MIME
340+ message [RFC2045][RFC2046][RFC5322] containing arbitrary octet-
341+ aligned material. This memo uses the mechanism described in the SMTP
342+ specification [RFC5321] to define an extension to the SMTP service
343+ whereby such contents may be exchanged. Note that this extension
344+ does NOT eliminate the possibility of an SMTP server limiting line
345+ length; servers are free to implement this extension but nevertheless
346+ set a line length limit no lower than 1000 octets. Given that this
347+ restriction still applies, this extension does NOT provide a means
348+ for transferring unencoded binary via SMTP.
349+
350+ 2. Framework for the 8-bit MIME Transport Extension
351+
352+ The 8-bit MIME transport extension is laid out as follows:
353+
354+ 1. the name of the SMTP service extension defined here is
355+ 8bit-MIMEtransport;
356+
357+ 2. the EHLO keyword value associated with the extension is 8BITMIME;
358+
359+ 3. no parameter is used with the 8BITMIME EHLO keyword;
360+
361+ 4. one optional parameter using the keyword BODY is added to the
362+ MAIL command. The value associated with this parameter is a
363+ keyword indicating whether a 7-bit message (in strict compliance
364+ with [RFC5321]) or a MIME message (in strict compliance with
365+ [RFC2046] and [RFC2045]) with arbitrary octet content is being
366+ sent. The syntax of the value is as follows, using the ABNF
367+ notation of [RFC5234]:
368+
369+ body-value = "7BIT" / "8BITMIME"
370+
371+ 5. no additional SMTP verbs are defined by this extension; and
372+
373+ 6. the next section specifies how support for the extension affects
374+ the behavior of a server and client SMTP.
375+
376+ 3. The 8bit-MIMEtransport Service Extension
377+
378+ When a client SMTP wishes to submit (using the MAIL command) a
379+ content body consisting of a MIME message containing arbitrary lines
380+ of octet-aligned material, it first issues the EHLO command to the
381+ server SMTP. If the server SMTP responds with code 250 to the EHLO
382+
383+
384+
385+ Klensin, et al. Standards Track [Page 2]
386+
387+ RFC 6152 SMTP Extension for 8-bit MIME March 2011
388+
389+
390+ command, and the response includes the EHLO keyword value 8BITMIME,
391+ then the server SMTP is indicating that it supports the extended MAIL
392+ command and will accept MIME messages containing arbitrary octet-
393+ aligned material.
394+
395+ The extended MAIL command is issued by a client SMTP when it wishes
396+ to transmit a content body consisting of a MIME message containing
397+ arbitrary lines of octet-aligned material. The syntax for this
398+ command is identical to the MAIL command in RFC 5321, except that a
399+ BODY parameter must appear after the address. Only one BODY
400+ parameter may be used in a single MAIL command.
401+
402+ The complete syntax of this extended command is defined in RFC 5321.
403+ The esmtp-keyword is BODY, and the syntax for esmtp-value is given by
404+ the syntax for body-value shown above.
405+
406+ The value associated with the BODY parameter indicates whether the
407+ content body that will be passed using the DATA command consists of a
408+ MIME message containing some arbitrary octet-aligned material
409+ ("8BITMIME") or is encoded entirely in accordance with RFC 5321
410+ ("7BIT").
411+
412+ A server that supports the 8-bit MIME transport service extension
413+ shall preserve all bits in each octet passed using the DATA command.
414+ Naturally, the usual SMTP data-stuffing algorithm applies, so that a
415+ content that contains the five-character sequence of
416+ <CR> <LF> <DOT> <CR> <LF>
417+ or a content that begins with the three-character sequence of
418+ <DOT> <CR> <LF>
419+ does not prematurely terminate the transfer of the content. Further,
420+ it should be noted that the CR-LF pair immediately preceding the
421+ final dot is considered part of the content. Finally, although the
422+ content body contains arbitrary lines of octet-aligned material, the
423+ length of each line (number of octets between two CR-LF pairs) is
424+ still subject to SMTP server line length restrictions (which can
425+ allow as few as 1000 octets, inclusive of the CR-LF pair, on a single
426+ line). This restriction means that this extension provides the
427+ necessary facilities for transferring a MIME object with the 8BIT
428+ content-transfer-encoding, it DOES NOT provide a means of
429+ transferring an object with the BINARY content-transfer-encoding.
430+
431+ Once a server SMTP supporting the 8bit-MIMEtransport service
432+ extension accepts a content body containing octets with the high-
433+ order (8th) bit set, the server SMTP must deliver or relay the
434+ content in such a way as to preserve all bits in each octet.
435+
436+
437+
438+
439+
440+
441+ Klensin, et al. Standards Track [Page 3]
442+
443+ RFC 6152 SMTP Extension for 8-bit MIME March 2011
444+
445+
446+ If a server SMTP does not support the 8-bit MIME transport extension
447+ (either by not responding with code 250 to the EHLO command, or by
448+ not including the EHLO keyword value 8BITMIME in its response), then
449+ the client SMTP must not, under any circumstances, attempt to
450+ transfer a content that contains characters outside of the US-ASCII
451+ octet range (hex 00-7F).
452+
453+ A client SMTP has two options in this case: first, it may implement a
454+ gateway transformation to convert the message into valid 7-bit MIME,
455+ or second, it may treat the barrier to 8-bit as a permanent error and
456+ handle it in the usual manner for delivery failures. The specifics
457+ of the transformation from 8-bit MIME to 7-bit MIME are not described
458+ by this RFC; the conversion is nevertheless constrained in the
459+ following ways:
460+
461+ 1. it must cause no loss of information; MIME transport encodings
462+ must be employed as needed to insure this is the case, and
463+
464+ 2. the resulting message must be valid 7-bit MIME.
465+
466+ 4. Usage Example
467+
468+ The following dialogue illustrates the use of the 8bit-MIMEtransport
469+ service extension:
470+
471+ S: <wait for connection on TCP port 25>
472+ C: <open connection to server>
473+ S: 220 dbc.mtview.ca.us SMTP service ready
474+ C: EHLO ymir.claremont.edu
475+ S: 250-dbc.mtview.ca.us says hello
476+ S: 250 8BITMIME
477+ C: MAIL FROM:<ned@ymir.claremont.edu> BODY=8BITMIME
478+ S: 250 <ned@ymir.claremont.edu>... Sender and 8BITMIME ok
479+ C: RCPT TO:<mrose@dbc.mtview.ca.us>
480+ S: 250 <mrose@dbc.mtview.ca.us>... Recipient ok
481+ C: DATA
482+ S: 354 Send 8BITMIME message, ending in CRLF.CRLF.
483+ ...
484+ C: .
485+ S: 250 OK
486+ C: QUIT
487+ S: 250 Goodbye
488+
489+
490+
491+
492+
493+
494+
495+
496+
497+ Klensin, et al. Standards Track [Page 4]
498+
499+ RFC 6152 SMTP Extension for 8-bit MIME March 2011
500+
501+
502+ 5. Security Considerations
503+
504+ This RFC does not discuss security issues and is not believed to
505+ raise any security issues not already endemic in electronic mail and
506+ present in fully conforming implementations of RFC 5321, including
507+ attacks facilitated by the presence of an option negotiation
508+ mechanism. Since MIME semantics are transport-neutral, the 8BITMIME
509+ option provides no more added capability to disseminate malware than
510+ is provided by unextended 7-bit SMTP.
511+
512+ 6. IANA Considerations
513+
514+ 6.1. SMTP Service Extension Registration
515+
516+ This document defines an SMTP and Submit service extension. IANA has
517+ updated the 8BITMIME entry in the SMTP Service Extensions registry,
518+ as follows:
519+
520+ Keyword: 8BITMIME
521+
522+ Description: SMTP and Submit transport of 8-bit MIME content
523+
524+ Reference: [RFC6152]
525+
526+ Parameters: See Section 2 in this specification.
527+
528+ 7. Acknowledgements
529+
530+ E. Stefferud was an original author. This version of the
531+ specification was produced by the YAM working group.
532+
533+ Original acknowledgements: This document represents a synthesis of
534+ the ideas of many people and reactions to the ideas and proposals
535+ of others. Randall Atkinson, Craig Everhart, Risto Kankkunen, and
536+ Greg Vaudreuil contributed ideas and text sufficient to be
537+ considered co-authors. Other important suggestions, text, or
538+ encouragement came from Harald Alvestrand, Jim Conklin,
539+ Mark Crispin, Frank da Cruz, Olafur Gudmundsson, Per Hedeland,
540+ Christian Huitma, Neil Katin, Eliot Lear, Harold A. Miller,
541+ Keith Moore, Dan Oscarsson, Julian Onions, Neil Rickert,
542+ John Wagner, Rayan Zachariassen, and the contributions of the
543+ entire IETF SMTP Working Group. Of course, none of the
544+ individuals are necessarily responsible for the combination of
545+ ideas represented here. Indeed, in some cases, the response to a
546+ particular criticism was to accept the problem identification but
547+ to include an entirely different solution from the one originally
548+ proposed.
549+
550+
551+
552+
553+ Klensin, et al. Standards Track [Page 5]
554+
555+ RFC 6152 SMTP Extension for 8-bit MIME March 2011
556+
557+
558+ 8. Normative References
559+
560+ [RFC2045] Freed, N. and N. Borenstein, "Multipurpose Internet Mail
561+ Extensions (MIME) Part One: Format of Internet Message
562+ Bodies", RFC 2045, November 1996.
563+
564+ [RFC2046] Freed, N. and N. Borenstein, "Multipurpose Internet Mail
565+ Extensions (MIME) Part Two: Media Types", RFC 2046,
566+ November 1996.
567+
568+ [RFC5234] Crocker, D. and P. Overell, "Augmented BNF for Syntax
569+ Specifications: ABNF", STD 68, RFC 5234, January 2008.
570+
571+ [RFC5321] Klensin, J., "Simple Mail Transfer Protocol", RFC 5321,
572+ October 2008.
573+
574+ [RFC5322] Resnick, P., Ed., "Internet Message Format", RFC 5322,
575+ October 2008.
576+
577+
578+
579+
580+
581+
582+
583+
584+
585+
586+
587+
588+
589+
590+
591+
592+
593+
594+
595+
596+
597+
598+
599+
600+
601+
602+
603+
604+
605+
606+
607+
608+
609+ Klensin, et al. Standards Track [Page 6]
610+
611+ RFC 6152 SMTP Extension for 8-bit MIME March 2011
612+
613+
614+ Authors' Addresses
615+
616+ John C. Klensin
617+ 1770 Massachusetts Ave, Ste. 322
618+ Cambridge, MA 02140
619+ USA
620+
621+ Phone: +1 617 245 1457
622+ EMail: john+ietf@jck.com
623+
624+
625+ Ned Freed
626+ Oracle
627+ 800 Royal Oaks
628+ Monrovia, CA 91016-6347
629+ USA
630+
631+ EMail: ned.freed@mrochek.com
632+
633+
634+ M. Rose
635+ Dover Beach Consulting, Inc.
636+ POB 255268
637+ Sacramento, CA 95865-5268
638+ USA
639+
640+ Phone: +1 916 538 2535
641+ EMail: mrose17@gmail.com
642+
643+
644+ D. Crocker (editor)
645+ Brandenburg InternetWorking
646+ 675 Spruce Dr.
647+ Sunnyvale, CA
648+ USA
649+
650+ Phone: +1 408 246 8253
651+ EMail: dcrocker@bbiw.net
652+ URI: http://bbiw.net
653+
654+
655+
656+
657+
658+
659+
660+
661+
662+
663+
664+
665+ Klensin, et al. Standards Track [Page 7]
666+