go-mail has insufficient address encoding when passing mail addresses to the SMTP client
Description
go-mail is a comprehensive library for sending mails with Go. In versions 0.7.0 and below, due to incorrect handling of the mail.Address values when a sender- or recipient address is passed to the corresponding MAIL FROM or RCPT TO commands of the SMTP client, there is a possibility of wrong address routing or even ESMTP parameter smuggling. For successful exploitation, it is required that the user's code allows for arbitrary mail address input (i. e. through a web form or similar). If only static mail addresses are used (i. e. in a config file) and the mail addresses in use do not consist of quoted local parts, this should not affect users. This issue is fixed in version 0.7.1
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
go-mail library v0.7.0 and below has an address parsing flaw enabling ESMTP parameter smuggling and misrouting.
Vulnerability
Description CVE-2025-59937 is an address parsing vulnerability in the go-mail library (versions 0.7.0 and earlier) that occurs when user-supplied mail addresses are passed to the SMTP client's MAIL FROM or RCPT TO commands. The library incorrectly handles quoted local-parts containing '@', allowing an attacker to craft an email address that causes the parser to extract and route to an unintended domain instead of the RFC-compliant target [1][3].
Exploitation
Successful exploitation requires that the application allows arbitrary mail address input (e.g., from a web form). If only static addresses from a configuration file are used and these addresses do not contain quoted local parts, the vulnerability is not exploitable [1]. An example payload such as "foo@psres.net> ORCPT=admin@admin.com"@test.com demonstrates how the parser misinterprets the address, leading to delivery to the wrong domain (foo@psres.net) instead of test.com [3]. This can also be leveraged for ESMTP parameter smuggling, injecting additional SMTP commands or parameters.
Impact
An attacker can cause email messages to be misrouted to unintended recipients or inject arbitrary ESMTP parameters, potentially enabling further SMTP-level attacks. The impact includes confidentiality violations (sensitive data sent to wrong address) and integrity issues (injection of SMTP commands).
Mitigation
The issue is fixed in version 0.7.1 of the library [1][4]. Users are advised to upgrade immediately. For those unable to upgrade, they should ensure that mail addresses are not taken from untrusted sources and that any dynamic addresses are validated according to RFC 5322.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/wneessen/go-mailGo | < 0.7.1 | 0.7.1 |
Affected products
2- wneessen/go-mailv5Range: < 0.7.1
Patches
142e92cfe027bMerge pull request #496 from wneessen/bugfix/495_mail-address-parsing
8 files changed · +356 −62
b64linebreaker.go+7 −7 modified@@ -42,7 +42,7 @@ type base64LineBreaker struct { func (l *base64LineBreaker) Write(data []byte) (numBytes int, err error) { if l.out == nil { err = errors.New("no io.Writer set for base64LineBreaker") - return + return numBytes, err } if l.used+len(data) < MaxBodyLength { copy(l.line[l.used:], data) @@ -52,25 +52,25 @@ func (l *base64LineBreaker) Write(data []byte) (numBytes int, err error) { _, err = l.out.Write(l.line[0:l.used]) if err != nil { - return + return numBytes, err } excess := MaxBodyLength - l.used l.used = 0 numBytes, err = l.out.Write(data[0:excess]) if err != nil { - return + return numBytes, err } _, err = l.out.Write(newlineBytes) if err != nil { - return + return numBytes, err } var n int n, err = l.Write(data[excess:]) // recurse numBytes += n - return + return numBytes, err } // Close finalizes the base64LineBreaker, writing any remaining buffered data and appending a newline. @@ -85,10 +85,10 @@ func (l *base64LineBreaker) Close() (err error) { if l.used > 0 { _, err = l.out.Write(l.line[0:l.used]) if err != nil { - return + return err } _, err = l.out.Write(newlineBytes) } - return + return err }
client.go+2 −2 modified@@ -1261,7 +1261,7 @@ func (c *Client) SendWithSMTPClient(client *smtp.Client, messages ...*Msg) (retu Reason: ErrConnCheck, errlist: []error{err}, isTemp: isTempError(err), errcode: errorCode(err), enhancedStatusCode: enhancedStatusCode(err, escSupport), } - return + return returnErr } var errs []error @@ -1279,7 +1279,7 @@ func (c *Client) SendWithSMTPClient(client *smtp.Client, messages ...*Msg) (retu } } - return + return returnErr } // auth attempts to authenticate the client using SMTP AUTH mechanisms. It checks the connection,
doc.go+1 −1 modified@@ -11,4 +11,4 @@ package mail // VERSION indicates the current version of the package. It is also attached to the default user // agent string. -const VERSION = "0.7.0" +const VERSION = "0.7.1"
eml.go+2 −2 modified@@ -538,7 +538,7 @@ func parseMultiPartHeader(multiPartHeader string) (header string, optional map[s headerSplit := strings.Split(multiPartHeader, ";") header = headerSplit[0] if len(headerSplit) == 1 { - return + return header, optional } for _, opt := range headerSplit[1:] { optString := strings.TrimLeft(opt, " ") @@ -547,7 +547,7 @@ func parseMultiPartHeader(multiPartHeader string) (header string, optional map[s optional[optSplit[0]] = optSplit[1] } } - return + return header, optional } // parseEMLAttachmentEmbed parses a multipart that is an attachment or embed.
msg.go+7 −4 modified@@ -1495,10 +1495,12 @@ func (m *Msg) GetSender(useFullAddr bool) (string, error) { return "", ErrNoFromAddress } } - if useFullAddr { - return from[0].String(), nil + + addr := from[0] + if !useFullAddr { + addr.Name = "" } - return from[0].Address, nil + return addr.String(), nil } // GetRecipients returns a list of the currently set "TO", "CC", and "BCC" addresses for the Msg. @@ -1522,7 +1524,8 @@ func (m *Msg) GetRecipients() ([]string, error) { continue } for _, r := range addresses { - rcpts = append(rcpts, r.Address) + r.Name = "" + rcpts = append(rcpts, r.String()) } } if len(rcpts) <= 0 {
msg_test.go+272 −30 modified@@ -2539,8 +2539,8 @@ func TestMsg_GetSender(t *testing.T) { if err != nil { t.Errorf("failed to get sender: %s", err) } - if !strings.EqualFold(sender, "toni.tester@example.com") { - t.Errorf("expected sender not returned. Want: %s, got: %s", "toni.tester@example.com", sender) + if !strings.EqualFold(sender, "<toni.tester@example.com>") { + t.Errorf("expected sender not returned. Want: %s, got: %s", "<toni.tester@example.com>", sender) } }) t.Run("GetSender with envelope from only (full address)", func(t *testing.T) { @@ -2571,8 +2571,8 @@ func TestMsg_GetSender(t *testing.T) { if err != nil { t.Errorf("failed to get sender: %s", err) } - if !strings.EqualFold(sender, "toni.tester@example.com") { - t.Errorf("expected sender not returned. Want: %s, got: %s", "toni.tester@example.com", sender) + if !strings.EqualFold(sender, "<toni.tester@example.com>") { + t.Errorf("expected sender not returned. Want: %s, got: %s", "<toni.tester@example.com>", sender) } }) t.Run("GetSender with from only (full address)", func(t *testing.T) { @@ -2606,8 +2606,8 @@ func TestMsg_GetSender(t *testing.T) { if err != nil { t.Errorf("failed to get sender: %s", err) } - if !strings.EqualFold(sender, "toni.tester@example.com") { - t.Errorf("expected sender not returned. Want: %s, got: %s", "toni.tester@example.com", sender) + if !strings.EqualFold(sender, "<toni.tester@example.com>") { + t.Errorf("expected sender not returned. Want: %s, got: %s", "<toni.tester@example.com>", sender) } }) t.Run("GetSender with envelope from and from (full address)", func(t *testing.T) { @@ -2661,9 +2661,67 @@ func TestMsg_GetRecipients(t *testing.T) { if len(rcpts) != 1 { t.Fatalf("expected 1 recipient, got: %d", len(rcpts)) } - if !strings.EqualFold(rcpts[0], "toni.tester@example.com") { + if !strings.EqualFold(rcpts[0], "<toni.tester@example.com>") { + t.Errorf("expected recipient not returned. Want: %s, got: %s", + "<toni.tester@example.com>", rcpts[0]) + } + }) + t.Run("GetRecipients with quoted local-part (issue #495)", func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + addr := `"toni.tester@example.com> ORCPT=admin@admin.com"@example.com` + if err := message.To(addr); err != nil { + t.Fatalf("failed to set to address: %s", err) + } + rcpts, err := message.GetRecipients() + if err != nil { + t.Errorf("failed to get recipients: %s", err) + } + if len(rcpts) != 1 { + t.Fatalf("expected 1 recipient, got: %d", len(rcpts)) + } + if !strings.EqualFold(rcpts[0], `<"toni.tester@example.com> ORCPT=admin@admin.com"@example.com>`) { + t.Errorf("expected recipient not returned. Want: %s, got: %s", + `<"toni.tester@example.com> ORCPT=admin@admin.com"@example.com>`, rcpts[0]) + } + }) + t.Run("GetRecipients with quoted local-part in to,cc and bcc (issue #495)", func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + addr := `"toni.tester@example.com> ORCPT=admin@admin.com"@example.com` + if err := message.To(addr); err != nil { + t.Fatalf("failed to set to address: %s", err) + } + addr = `"tina.tester@example.com> ORCPT=admin@admin.com"@example.com` + if err := message.Cc(addr); err != nil { + t.Fatalf("failed to set to address: %s", err) + } + addr = `"troy.tester@example.com> ORCPT=admin@admin.com"@example.com` + if err := message.Bcc(addr); err != nil { + t.Fatalf("failed to set to address: %s", err) + } + rcpts, err := message.GetRecipients() + if err != nil { + t.Errorf("failed to get recipients: %s", err) + } + if len(rcpts) != 3 { + t.Fatalf("expected 3 recipient, got: %d", len(rcpts)) + } + if !strings.EqualFold(rcpts[0], `<"toni.tester@example.com> ORCPT=admin@admin.com"@example.com>`) { + t.Errorf("expected recipient not returned. Want: %s, got: %s", + `<"toni.tester@example.com> ORCPT=admin@admin.com"@example.com>`, rcpts[0]) + } + if !strings.EqualFold(rcpts[1], `<"tina.tester@example.com> ORCPT=admin@admin.com"@example.com>`) { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "toni.tester@example.com", rcpts[0]) + `<"tina.tester@example.com> ORCPT=admin@admin.com"@example.com>`, rcpts[1]) + } + if !strings.EqualFold(rcpts[2], `<"troy.tester@example.com> ORCPT=admin@admin.com"@example.com>`) { + t.Errorf("expected recipient not returned. Want: %s, got: %s", + `<"troy.tester@example.com> ORCPT=admin@admin.com"@example.com>`, rcpts[2]) } }) t.Run("GetRecipients with only cc", func(t *testing.T) { @@ -2681,9 +2739,9 @@ func TestMsg_GetRecipients(t *testing.T) { if len(rcpts) != 1 { t.Fatalf("expected 1 recipient, got: %d", len(rcpts)) } - if !strings.EqualFold(rcpts[0], "toni.tester@example.com") { + if !strings.EqualFold(rcpts[0], "<toni.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "toni.tester@example.com", rcpts[0]) + "<toni.tester@example.com>", rcpts[0]) } }) t.Run("GetRecipients with only bcc", func(t *testing.T) { @@ -2701,9 +2759,9 @@ func TestMsg_GetRecipients(t *testing.T) { if len(rcpts) != 1 { t.Fatalf("expected 1 recipient, got: %d", len(rcpts)) } - if !strings.EqualFold(rcpts[0], "toni.tester@example.com") { + if !strings.EqualFold(rcpts[0], "<toni.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "toni.tester@example.com", rcpts[0]) + "<toni.tester@example.com>", rcpts[0]) } }) t.Run("GetRecipients with to and cc", func(t *testing.T) { @@ -2724,13 +2782,13 @@ func TestMsg_GetRecipients(t *testing.T) { if len(rcpts) != 2 { t.Fatalf("expected 2 recipient, got: %d", len(rcpts)) } - if !strings.EqualFold(rcpts[0], "toni.tester@example.com") { + if !strings.EqualFold(rcpts[0], "<toni.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "toni.tester@example.com", rcpts[0]) + "<toni.tester@example.com>", rcpts[0]) } - if !strings.EqualFold(rcpts[1], "tina.tester@example.com") { + if !strings.EqualFold(rcpts[1], "<tina.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "tina.tester@example.com", rcpts[1]) + "<tina.tester@example.com>", rcpts[1]) } }) t.Run("GetRecipients with to and bcc", func(t *testing.T) { @@ -2751,13 +2809,13 @@ func TestMsg_GetRecipients(t *testing.T) { if len(rcpts) != 2 { t.Fatalf("expected 2 recipient, got: %d", len(rcpts)) } - if !strings.EqualFold(rcpts[0], "toni.tester@example.com") { + if !strings.EqualFold(rcpts[0], "<toni.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "toni.tester@example.com", rcpts[0]) + "<toni.tester@example.com>", rcpts[0]) } - if !strings.EqualFold(rcpts[1], "tina.tester@example.com") { + if !strings.EqualFold(rcpts[1], "<tina.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "tina.tester@example.com", rcpts[1]) + "<tina.tester@example.com>", rcpts[1]) } }) t.Run("GetRecipients with cc and bcc", func(t *testing.T) { @@ -2778,13 +2836,13 @@ func TestMsg_GetRecipients(t *testing.T) { if len(rcpts) != 2 { t.Fatalf("expected 2 recipient, got: %d", len(rcpts)) } - if !strings.EqualFold(rcpts[0], "toni.tester@example.com") { + if !strings.EqualFold(rcpts[0], "<toni.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "toni.tester@example.com", rcpts[0]) + "<toni.tester@example.com>", rcpts[0]) } - if !strings.EqualFold(rcpts[1], "tina.tester@example.com") { + if !strings.EqualFold(rcpts[1], "<tina.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "tina.tester@example.com", rcpts[1]) + "<tina.tester@example.com>", rcpts[1]) } }) t.Run("GetRecipients with to, cc and bcc", func(t *testing.T) { @@ -2808,17 +2866,17 @@ func TestMsg_GetRecipients(t *testing.T) { if len(rcpts) != 3 { t.Fatalf("expected 3 recipient, got: %d", len(rcpts)) } - if !strings.EqualFold(rcpts[0], "toni.tester@example.com") { + if !strings.EqualFold(rcpts[0], "<toni.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "toni.tester@example.com", rcpts[0]) + "<toni.tester@example.com>", rcpts[0]) } - if !strings.EqualFold(rcpts[1], "tina.tester@example.com") { + if !strings.EqualFold(rcpts[1], "<tina.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "tina.tester@example.com", rcpts[1]) + "<tina.tester@example.com>", rcpts[1]) } - if !strings.EqualFold(rcpts[2], "tom.tester@example.com") { + if !strings.EqualFold(rcpts[2], "<tom.tester@example.com>") { t.Errorf("expected recipient not returned. Want: %s, got: %s", - "tina.tester@example.com", rcpts[2]) + "<tina.tester@example.com>", rcpts[2]) } }) t.Run("GetRecipients with no recipients", func(t *testing.T) { @@ -2836,6 +2894,190 @@ func TestMsg_GetRecipients(t *testing.T) { }) } +func TestMsg_addressCmdInjectsions(t *testing.T) { + tests := []struct { + name string + payload string + shouldFail bool + }{ + // Basic argument-injection (expected to fail) + {"ORCPT-arg", `"toni.tester@example.com> ORCPT=admin@example.com"@example.com`, false}, + {"SIZE-arg", `"toni.tester@example.com> SIZE=99999"@example.com`, false}, + {"AUTH-arg", `"toni.tester@example.com> AUTH=PLAIN"@example.com`, false}, + + // whitespace / separator variants + {"double-space", `"toni.tester@example.com> ORCPT=admin@example.com"@example.com`, false}, + {"tab-separator", `"toni.tester@example.com>\tORCPT=admin@example.com"@example.com`, false}, + + // quoted/escape tricks + {"quoted-close-space", `"toni.tester@example.com\"> ORCPT=admin@example.com"@example.com`, false}, + {"escaped-backslash", `"toni.tester@example.com\\"> ORCPT=admin@example.com"@example.com`, true}, + + // missing / mis-balanced angle brackets + {"unbalanced-close", `toni.tester@example.com> ORCPT=admin@example.com@example.com`, true}, + {"leading-bracket", `<toni.tester@example.com> ORCPT=admin@example.com@example.com`, true}, + {"no-brackets", `toni.tester@example.com ORCPT=admin@example.com@example.com`, true}, + + // comments / RFC-style + {"comment-insert", `(comment)toni.tester@example.com> ORCPT=admin@example.com"@example.com`, true}, + {"comment-inside-quote", `"toni.tester@example.com (orcd) > ORCPT=admin@example.com"@example.com`, false}, + + // percent-encoded attempts + {"percent-encoded-sep", `"toni.tester@example.com%3E%20ORCPT=admin@example.com"@example.com`, false}, + {"percent-encoded-crlf", `"toni.tester@example.com%0d%0aRCPT TO:<other@example.com>"@example.com`, false}, + + // null / control byte insertion + {"null-byte", `"toni.tester@example.com\000> ORCPT=admin@example.com"@example.com`, false}, + + // explicit CRLF-injection attempts + {"crlf-rcpt", `"toni.tester@example.com">\r\nRCPT TO:<attacker@example.com>"@example.com`, true}, + {"crlf-mailfrom", `"toni.tester@example.com">\r\nMAIL FROM:<attacker@example.com>"@example.com`, true}, + + // folding whitespace / long-wrapping + {"folding-ws", `"toni.tester@example.com> \r\n\tORCPT=admin@example.com"@example.com`, false}, + + // unicode / homoglyphs + {"unicode-fullwidth", `"toni.tester@example.com> ORCPT=admin@example.com"@example.com`, false}, + {"unicode-hidden", `"toni.tester@example.com> ORCPT=admin@exam` + "\u200c" + `ple.com"@example.com`, false}, + + // multiple @ / nested-at attempts + {"nested-at", `"toni.tester@example.com> ORCPT=admin@sub@example.com"@example.com`, false}, + {"double-at", `"toni.tester@example.com> ORCPT=admin@@example.com"@example.com`, false}, + + // long / overflow-ish + {"long-localpart", `"toni.tester@example.comAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA> ORCPT=admin@example.com"@example.com`, false}, + + // safe detection-only marker + {"detection-marker", `"toni.tester@example.com> X-INJECT-TEST=smuggle-detect@example.com"@example.com`, false}, + } + + t.Run("address command injects in recipients", func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + err := message.To(tc.payload) + if err != nil && !tc.shouldFail { + t.Errorf("failed to set to address: %s", err) + } + if err == nil && tc.shouldFail { + t.Errorf("setting address was supposed to fail but didn't") + } + if tc.shouldFail { + return + } + + addr, err := mail.ParseAddress(tc.payload) + if err != nil { + t.Errorf("failed to parse address payload: %s", err) + } + rcpts, err := message.GetRecipients() + if err != nil { + t.Errorf("failed to get recipient: %s", err) + } + if len(rcpts) != 1 { + t.Fatalf("expected 1 recipient, got: %d", len(rcpts)) + } + if !strings.EqualFold(rcpts[0], addr.String()) { + t.Errorf("recipients don't match, expected: %s, got: %s", addr.String(), rcpts[0]) + } + }) + } + }) + t.Run("address command injects in sender (from)", func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + err := message.From(tc.payload) + if err != nil && !tc.shouldFail { + t.Errorf("failed to set to address: %s", err) + } + if err == nil && tc.shouldFail { + t.Errorf("setting address was supposed to fail but didn't") + } + if tc.shouldFail { + return + } + + addr, err := mail.ParseAddress(tc.payload) + if err != nil { + t.Errorf("failed to parse address payload: %s", err) + } + from, err := message.GetSender(false) + if err != nil { + t.Errorf("failed to get recipient: %s", err) + } + if from == "" { + t.Fatal("expected sender, got empty string") + } + if !strings.EqualFold(from, addr.String()) { + t.Errorf("sender don't match, expected: %s, got: %s", addr.String(), from) + } + from, err = message.GetSender(true) + if err != nil { + t.Errorf("failed to get recipient: %s", err) + } + if from == "" { + t.Fatal("expected sender, got empty string") + } + if !strings.EqualFold(from, addr.String()) { + t.Errorf("sender don't match, expected: %s, got: %s", addr.String(), from) + } + }) + } + }) + t.Run("address command injects in sender (envelope-from)", func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + err := message.EnvelopeFrom(tc.payload) + if err != nil && !tc.shouldFail { + t.Errorf("failed to set to address: %s", err) + } + if err == nil && tc.shouldFail { + t.Errorf("setting address was supposed to fail but didn't") + } + if tc.shouldFail { + return + } + + addr, err := mail.ParseAddress(tc.payload) + if err != nil { + t.Errorf("failed to parse address payload: %s", err) + } + from, err := message.GetSender(false) + if err != nil { + t.Errorf("failed to get recipient: %s", err) + } + if from == "" { + t.Fatal("expected sender, got empty string") + } + if !strings.EqualFold(from, addr.String()) { + t.Errorf("sender don't match, expected: %s, got: %s", addr.String(), from) + } + from, err = message.GetSender(true) + if err != nil { + t.Errorf("failed to get recipient: %s", err) + } + if from == "" { + t.Fatal("expected sender, got empty string") + } + if !strings.EqualFold(from, addr.String()) { + t.Errorf("sender don't match, expected: %s, got: %s", addr.String(), from) + } + }) + } + }) +} + func TestMsg_GetAddrHeader(t *testing.T) { t.Run("GetAddrHeader with valid address (from)", func(t *testing.T) { message := NewMsg()
smtp/smtp.go+6 −6 modified@@ -266,10 +266,10 @@ func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) { tc, ok := c.conn.(*tls.Conn) if !ok { - return + return state, ok } state, ok = tc.ConnectionState(), true - return + return state, ok } // Verify checks the validity of an email address on the server. @@ -368,7 +368,7 @@ func (c *Client) Mail(from string) error { if err := c.hello(); err != nil { return err } - cmdStr := "MAIL FROM:<%s>" + cmdStr := "MAIL FROM:%s" c.mutex.RLock() if c.ext != nil { @@ -402,10 +402,10 @@ func (c *Client) Rcpt(to string) error { c.mutex.RUnlock() if ok && c.dsnrntype != "" { - _, _, err := c.cmd(25, "RCPT TO:<%s> NOTIFY=%s", to, c.dsnrntype) + _, _, err := c.cmd(25, "RCPT TO:%s NOTIFY=%s", to, c.dsnrntype) return err } - _, _, err := c.cmd(25, "RCPT TO:<%s>", to) + _, _, err := c.cmd(25, "RCPT TO:%s", to) return err } @@ -432,7 +432,7 @@ func (d *DataCloser) Write(p []byte) (n int, err error) { d.c.mutex.Lock() n, err = d.WriteCloser.Write(p) d.c.mutex.Unlock() - return + return n, err } // ServerResponse returns the response that was returned by the server after the DataCloser has
smtp/smtp_test.go+59 −10 modified@@ -30,6 +30,7 @@ import ( "hash" "io" "net" + netmail "net/mail" "os" "strconv" "strings" @@ -2261,7 +2262,11 @@ func TestClient_Mail(t *testing.T) { t.Errorf("failed to close client: %s", err) } }) - if err = client.Mail("valid-from@domain.tld"); err != nil { + fromAddr, err := netmail.ParseAddress("valid-from@domain.tld") + if err != nil { + t.Fatalf("failed to parse from address: %s", err) + } + if err = client.Mail(fromAddr.String()); err != nil { t.Errorf("failed to set mail from address: %s", err) } }) @@ -2358,7 +2363,11 @@ func TestClient_Mail(t *testing.T) { t.Errorf("failed to close client: %s", err) } }) - if err = client.Mail("valid-from@domain.tld"); err != nil { + fromAddr, err := netmail.ParseAddress("valid-from@domain.tld") + if err != nil { + t.Fatalf("failed to parse from address: %s", err) + } + if err = client.Mail(fromAddr.String()); err != nil { t.Errorf("failed to set mail from address: %s", err) } expected := "MAIL FROM:<valid-from@domain.tld> BODY=8BITMIME" @@ -2398,7 +2407,11 @@ func TestClient_Mail(t *testing.T) { t.Errorf("failed to close client: %s", err) } }) - if err = client.Mail("valid-from@domain.tld"); err != nil { + fromAddr, err := netmail.ParseAddress("valid-from@domain.tld") + if err != nil { + t.Fatalf("failed to parse from address: %s", err) + } + if err = client.Mail(fromAddr.String()); err != nil { t.Errorf("failed to set mail from address: %s", err) } expected := "MAIL FROM:<valid-from@domain.tld> SMTPUTF8" @@ -2438,7 +2451,11 @@ func TestClient_Mail(t *testing.T) { t.Errorf("failed to close client: %s", err) } }) - if err = client.Mail("valid-from+📧@domain.tld"); err != nil { + fromAddr, err := netmail.ParseAddress("valid-from+📧@domain.tld") + if err != nil { + t.Fatalf("failed to parse from address: %s", err) + } + if err = client.Mail(fromAddr.String()); err != nil { t.Errorf("failed to set mail from address: %s", err) } expected := "MAIL FROM:<valid-from+📧@domain.tld> SMTPUTF8" @@ -2479,7 +2496,11 @@ func TestClient_Mail(t *testing.T) { } }) client.dsnmrtype = "FULL" - if err = client.Mail("valid-from@domain.tld"); err != nil { + fromAddr, err := netmail.ParseAddress("valid-from@domain.tld") + if err != nil { + t.Fatalf("failed to parse from address: %s", err) + } + if err = client.Mail(fromAddr.String()); err != nil { t.Errorf("failed to set mail from address: %s", err) } expected := "MAIL FROM:<valid-from@domain.tld> RET=FULL" @@ -2520,7 +2541,11 @@ func TestClient_Mail(t *testing.T) { } }) client.dsnmrtype = "FULL" - if err = client.Mail("valid-from@domain.tld"); err != nil { + fromAddr, err := netmail.ParseAddress("valid-from@domain.tld") + if err != nil { + t.Fatalf("failed to parse from address: %s", err) + } + if err = client.Mail(fromAddr.String()); err != nil { t.Errorf("failed to set mail from address: %s", err) } expected := "MAIL FROM:<valid-from@domain.tld> BODY=8BITMIME SMTPUTF8 RET=FULL" @@ -2560,7 +2585,11 @@ func TestClient_Rcpt(t *testing.T) { t.Errorf("failed to close client: %s", err) } }) - if err = client.Rcpt("valid-to@domain.tld"); err != nil { + addr, err := netmail.ParseAddress("valid-to@domain.tld") + if err != nil { + t.Fatalf("failed to parse recipient address: %s", err) + } + if err = client.Rcpt(addr.String()); err != nil { t.Errorf("failed to set recipient address: %s", err) } }) @@ -2626,7 +2655,11 @@ func TestClient_Rcpt(t *testing.T) { t.Fatalf("failed to send hello to test server: %s", err) } client.dsnrntype = "SUCCESS" - if err = client.Rcpt("valid-to@domain.tld"); err == nil { + addr, err := netmail.ParseAddress("valid-to@domain.tld") + if err != nil { + t.Fatalf("failed to parse recipient address: %s", err) + } + if err = client.Rcpt(addr.String()); err == nil { t.Error("recpient address with newlines should fail") } expected := "RCPT TO:<valid-to@domain.tld> NOTIFY=SUCCESS" @@ -3006,8 +3039,16 @@ func TestSendMail(t *testing.T) { config.RootCAs = testConfig.RootCAs config.Certificates = testConfig.Certificates } + fromAddr, err := netmail.ParseAddress("valid-from@domain.tld") + if err != nil { + t.Fatalf("failed to parse from address: %s", err) + } auth := LoginAuth("username", "password", TestServerAddr, false) - if err := SendMail(addr, auth, "valid-from@domain.tld", []string{"valid-to@domain.tld"}, + toAddr, err := netmail.ParseAddress("valid-to@domain.tld") + if err != nil { + t.Fatalf("failed to parse recipient address: %s", err) + } + if err := SendMail(addr, auth, fromAddr.String(), []string{toAddr.String()}, []byte("test message")); err != nil { t.Fatalf("failed to send mail: %s", err) } @@ -3090,8 +3131,16 @@ Subject: Hooray for Go Line 1 .Leading dot line . Goodbye.`) + fromAddr, err := netmail.ParseAddress("valid-from@domain.tld") + if err != nil { + t.Fatalf("failed to parse from address: %s", err) + } + toAddr, err := netmail.ParseAddress("valid-to@domain.tld") + if err != nil { + t.Fatalf("failed to parse recipient address: %s", err) + } auth := LoginAuth("username", "password", TestServerAddr, false) - if err := SendMail(addr, auth, "valid-from@domain.tld", []string{"valid-to@domain.tld"}, message); err != nil { + if err = SendMail(addr, auth, fromAddr.String(), []string{toAddr.String()}, message); err != nil { t.Fatalf("failed to send mail: %s", err) } props.BufferMutex.RLock()
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
7- github.com/advisories/GHSA-wpwj-69cm-q9c5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-59937ghsaADVISORY
- github.com/wneessen/go-mail/commit/42e92cfe027be04aff72921adb0f72f11d517479ghsax_refsource_MISCWEB
- github.com/wneessen/go-mail/issues/495ghsax_refsource_MISCWEB
- github.com/wneessen/go-mail/pull/496ghsax_refsource_MISCWEB
- github.com/wneessen/go-mail/security/advisories/GHSA-wpwj-69cm-q9c5ghsax_refsource_CONFIRMWEB
- pkg.go.dev/vuln/GO-2025-3988ghsaWEB
News mentions
0No linked articles in our index yet.