diff --git a/backend.go b/backend.go index 97599df..e2af9af 100644 --- a/backend.go +++ b/backend.go @@ -21,9 +21,20 @@ type Backend interface { AnonymousLogin(state *ConnectionState) (Session, error) } +type BodyType string + +const ( + Body7Bit BodyType = "7BIT" + Body8BitMIME BodyType = "8BITMIME" + BodyBinaryMIME BodyType = "BINARYMIME" +) + // MailOptions contains custom arguments that were // passed as an argument to the MAIL command. type MailOptions struct { + // Value of BODY= argument, 7BIT, 8BITMIME or BINARYMIME. + Body BodyType + // Size of the body. Can be 0 if not specified by client. Size int diff --git a/conn.go b/conn.go index b7be1f4..87db096 100644 --- a/conn.go +++ b/conn.go @@ -260,6 +260,9 @@ func (c *Conn) handleGreet(enhanced bool, arg string) { if _, isTLS := c.TLSConnectionState(); isTLS && c.server.EnableREQUIRETLS { caps = append(caps, "REQUIRETLS") } + if c.server.EnableBINARYMIME { + caps = append(caps, "BINARYMIME") + } if c.server.MaxMessageBytes > 0 { caps = append(caps, fmt.Sprintf("SIZE %v", c.server.MaxMessageBytes)) } @@ -350,11 +353,17 @@ func (c *Conn) handleMail(arg string) { opts.RequireTLS = true case "BODY": switch value { + case "BINARYMIME": + if !c.server.EnableBINARYMIME { + c.WriteResponse(504, EnhancedCode{5, 5, 4}, "BINARYMIME is not implemented") + return + } case "7BIT", "8BITMIME": default: c.WriteResponse(500, EnhancedCode{5, 5, 4}, "Unknown BODY value") return } + opts.Body = BodyType(value) case "AUTH": value, err := decodeXtext(value) if err != nil { diff --git a/server.go b/server.go index 2632d39..4fa0b95 100755 --- a/server.go +++ b/server.go @@ -53,6 +53,10 @@ type Server struct { // Should be used only if backend supports it. EnableREQUIRETLS bool + // Advertise BINARYMIME (RFC 3030) capability. + // Should be used only if backend supports it. + EnableBINARYMIME bool + // If set, the AUTH command will not be advertised and authentication // attempts will be rejected. This setting overrides AllowInsecureAuth. AuthDisabled bool diff --git a/server_test.go b/server_test.go index fea010a..48ad654 100644 --- a/server_test.go +++ b/server_test.go @@ -1045,3 +1045,51 @@ func TestServer_Chunking_tooLongMessage(t *testing.T) { t.Fatal("Invalid number of sent messages:", be.messages, be.anonmsgs) } } + +func TestServer_Chunking_Binarymime(t *testing.T) { + be, s, c, scanner := testServerAuthenticated(t) + defer s.Close() + defer c.Close() + s.EnableBINARYMIME = true + + io.WriteString(c, "MAIL FROM: BODY=BINARYMIME\r\n") + scanner.Scan() + if !strings.HasPrefix(scanner.Text(), "250 ") { + t.Fatal("Invalid MAIL response:", scanner.Text()) + } + + io.WriteString(c, "RCPT TO:\r\n") + scanner.Scan() + if !strings.HasPrefix(scanner.Text(), "250 ") { + t.Fatal("Invalid RCPT response:", scanner.Text()) + } + + io.WriteString(c, "BDAT 8\r\n") + io.WriteString(c, "Hey <3\r\n") + scanner.Scan() + if !strings.HasPrefix(scanner.Text(), "250 ") { + t.Fatal("Invalid BDAT response:", scanner.Text()) + } + + io.WriteString(c, "BDAT 8 LAST\r\n") + io.WriteString(c, "Hey :3\r\n") + scanner.Scan() + if !strings.HasPrefix(scanner.Text(), "250 ") { + t.Fatal("Invalid BDAT response:", scanner.Text()) + } + + if len(be.messages) != 1 || len(be.anonmsgs) != 0 { + t.Fatal("Invalid number of sent messages:", be.messages, be.anonmsgs) + } + + msg := be.messages[0] + if msg.From != "root@nsa.gov" { + t.Fatal("Invalid mail sender:", msg.From) + } + if len(msg.To) != 1 || msg.To[0] != "root@gchq.gov.uk" { + t.Fatal("Invalid mail recipients:", msg.To) + } + if want := "Hey <3\r\nHey :3\r\n"; string(msg.Data) != want { + t.Fatal("Invalid mail data:", string(msg.Data), msg.Data) + } +} diff --git a/smtp.go b/smtp.go index e5045a7..77ef835 100644 --- a/smtp.go +++ b/smtp.go @@ -8,6 +8,8 @@ // ENHANCEDSTATUSCODES RFC 2034 // SMTPUTF8 RFC 6531 // REQUIRETLS draft-ietf-uta-smtp-require-tls-09 +// CHUNKING RFC 3030 +// BINARYMIME RFC 3030 // // LMTP (RFC 2033) is also supported. //