Repository: emersion/go-smtp
Pull request #226 (2023-07-14 15:38:45)

Update MAIL FROM AUTH handling to accept mailbox without angle brackets
I have a client that sends a message to an SMTP server using this library (the Protonmail bridge) and encounter problems because the client doesn't enclose the `AUTH=` parameter in angle brackets.

I looked at [the relevant RFC](https://datatracker.ietf.org/doc/html/rfc4954#section-5) and it doesn't appear that this parameter requires angle brackets. [RFC2821 (referenced by the other RFC) section 4.1.2](https://datatracker.ietf.org/doc/html/rfc2821#section-4.1.2) requires angle brackets for a _Path_ but not for a Mailbox. It explicitly allows Mailboxes without angle brackets:

```
Mailbox = Local-part "@" Domain
Local-part = Dot-string / Quoted-string
            ; MAY be case-sensitive
Dot-string = Atom *("." Atom)
Atom = 1*atext
(From https://datatracker.ietf.org/doc/html/rfc2822#section-3.2.4)
atext           =       ALPHA / DIGIT / ; Any character except controls,
                        "!" / "#" /     ;  SP, and specials.
                        "$" / "%" /     ;  Used for atoms
                        "&" / "'" /
                        "*" / "+" /
                        "-" / "/" /
                        "=" / "?" /
                        "^" / "_" /
                        "`" / "{" /
                        "|" / "}" /
                        "~"
```

In other words, the `Local-part` can be composed of a `Dot-string`, which is composed only alphanumerics plus some special characters (the list of which excludes angle brackets).

emersion: My reading of the spec is that angle brackets are not allowed here, apart from the special case of `<>`. I've edited the PR accordingly.

Patch:
diff --git a/conn.go b/conn.go
index 339f40b..a960b50 100644
--- a/conn.go
+++ b/conn.go
@@ -362,20 +362,15 @@ func (c *Conn) handleMail(arg string) {
 				opts.Body = BodyType(value)
 			case "AUTH":
 				value, err := decodeXtext(value)
-				if err != nil {
+				if err != nil || value == "" {
 					c.writeResponse(500, EnhancedCode{5, 5, 4}, "Malformed AUTH parameter value")
 					return
 				}
-				if !strings.HasPrefix(value, "<") {
-					c.writeResponse(500, EnhancedCode{5, 5, 4}, "Missing opening angle bracket")
-					return
-				}
-				if !strings.HasSuffix(value, ">") {
-					c.writeResponse(500, EnhancedCode{5, 5, 4}, "Missing closing angle bracket")
-					return
+				if value == "<>" {
+					value = ""
 				}
-				decodedMbox := value[1 : len(value)-1]
-				opts.Auth = &decodedMbox
+				// TODO: otherwise, check mailbox syntax (RFC 2821 section 4.1.2)
+				opts.Auth = &value
 			default:
 				c.writeResponse(500, EnhancedCode{5, 5, 4}, "Unknown MAIL FROM argument")
 				return


Test patch:
diff --git a/server_test.go b/server_test.go
index 0db69a8..12036b6 100644
--- a/server_test.go
+++ b/server_test.go
@@ -802,7 +802,7 @@ func TestServer_authParam(t *testing.T) {
 	// >extension MUST support the AUTH parameter to the MAIL FROM
 	// >command even when the client has not authenticated itself to the
 	// >server.
-	io.WriteString(c, "MAIL FROM: root@nsa.gov AUTH=<hey+3Da>\r\n")
+	io.WriteString(c, "MAIL FROM: root@nsa.gov AUTH=hey+3Da@example.com\r\n")
 	scanner.Scan()
 	if !strings.HasPrefix(scanner.Text(), "250 ") {
 		t.Fatal("Invalid MAIL response:", scanner.Text())
@@ -823,7 +823,7 @@ func TestServer_authParam(t *testing.T) {
 	if len(be.messages) != 0 || len(be.anonmsgs) != 1 {
 		t.Fatal("Invalid number of sent messages:", be.messages, be.anonmsgs)
 	}
-	if val := be.anonmsgs[0].Opts.Auth; val == nil || *val != "hey=a" {
+	if val := be.anonmsgs[0].Opts.Auth; val == nil || *val != "hey=a@example.com" {
 		t.Fatal("Invalid Auth value:", val)
 	}
 }