From 05ad5d147d2521c0b11298129551e6d2316547d5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 28 Nov 2025 16:56:18 -0800 Subject: [PATCH 1/2] Use Golang net/smtp instead of gomail's smtp to send email --- services/mailer/sender/smtp.go | 38 ++++++++++++++++++++--------- services/mailer/sender/smtp_auth.go | 2 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/services/mailer/sender/smtp.go b/services/mailer/sender/smtp.go index 3207eee32fcec..122c7333f6439 100644 --- a/services/mailer/sender/smtp.go +++ b/services/mailer/sender/smtp.go @@ -9,13 +9,13 @@ import ( "fmt" "io" "net" + "net/mail" + "net/smtp" "os" "strings" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - - "github.com/wneessen/go-mail/smtp" ) // SMTPSender Sender SMTP mail sender @@ -108,7 +108,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { if strings.Contains(options, "CRAM-MD5") { auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd) } else if strings.Contains(options, "PLAIN") { - auth = smtp.PlainAuth("", opts.User, opts.Passwd, host, false) + auth = smtp.PlainAuth("", opts.User, opts.Passwd, host) } else if strings.Contains(options, "LOGIN") { // Patch for AUTH LOGIN auth = LoginAuth(opts.User, opts.Passwd) @@ -123,18 +123,24 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { } } - if opts.OverrideEnvelopeFrom { - if err = client.Mail(opts.EnvelopeFrom); err != nil { - return fmt.Errorf("failed to issue MAIL command: %w", err) - } - } else { - if err = client.Mail(fmt.Sprintf("<%s>", from)); err != nil { - return fmt.Errorf("failed to issue MAIL command: %w", err) - } + fromAddr := from + if opts.OverrideEnvelopeFrom && opts.EnvelopeFrom != "" { + fromAddr = opts.EnvelopeFrom + } + smtpFrom, err := sanitizeSMTPAddress(fromAddr) + if err != nil { + return fmt.Errorf("invalid envelope from address: %w", err) + } + if err = client.Mail(smtpFrom); err != nil { + return fmt.Errorf("failed to issue MAIL command: %w", err) } for _, rec := range to { - if err = client.Rcpt(rec); err != nil { + smtpTo, err := sanitizeSMTPAddress(rec) + if err != nil { + return fmt.Errorf("invalid recipient address %q: %w", rec, err) + } + if err = client.Rcpt(smtpTo); err != nil { return fmt.Errorf("failed to issue RCPT command: %w", err) } } @@ -155,3 +161,11 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { return nil } + +func sanitizeSMTPAddress(raw string) (string, error) { + addr, err := mail.ParseAddress(strings.TrimSpace(strings.Trim(raw, "<>"))) + if err != nil { + return "", err + } + return addr.Address, nil +} diff --git a/services/mailer/sender/smtp_auth.go b/services/mailer/sender/smtp_auth.go index c60e0dbfbbe6f..66ea24e896033 100644 --- a/services/mailer/sender/smtp_auth.go +++ b/services/mailer/sender/smtp_auth.go @@ -6,9 +6,9 @@ package sender import ( "errors" "fmt" + "net/smtp" "github.com/Azure/go-ntlmssp" - "github.com/wneessen/go-mail/smtp" ) type loginAuth struct { From e708b564a05df3fe15a2626bae3d13e261f16b73 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 29 Nov 2025 12:13:27 -0800 Subject: [PATCH 2/2] fix sendmail to recipient --- services/mailer/sender/sendmail.go | 8 +++++++- services/mailer/sender/smtp.go | 6 +++--- services/mailer/sender/smtp_test.go | 30 +++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 services/mailer/sender/smtp_test.go diff --git a/services/mailer/sender/sendmail.go b/services/mailer/sender/sendmail.go index 64c7f8f0816b4..7064c60f97638 100644 --- a/services/mailer/sender/sendmail.go +++ b/services/mailer/sender/sendmail.go @@ -33,7 +33,13 @@ func (s *SendmailSender) Send(from string, to []string, msg io.WriterTo) error { args := []string{"-f", envelopeFrom, "-i"} args = append(args, setting.MailService.SendmailArgs...) - args = append(args, to...) + for _, recipient := range to { + smtpTo, err := sanitizeEmailAddress(recipient) + if err != nil { + return fmt.Errorf("invalid recipient address %q: %w", recipient, err) + } + args = append(args, smtpTo) + } log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args) desc := fmt.Sprintf("SendMail: %s %v", setting.MailService.SendmailPath, args) diff --git a/services/mailer/sender/smtp.go b/services/mailer/sender/smtp.go index 122c7333f6439..fa07803359cad 100644 --- a/services/mailer/sender/smtp.go +++ b/services/mailer/sender/smtp.go @@ -127,7 +127,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { if opts.OverrideEnvelopeFrom && opts.EnvelopeFrom != "" { fromAddr = opts.EnvelopeFrom } - smtpFrom, err := sanitizeSMTPAddress(fromAddr) + smtpFrom, err := sanitizeEmailAddress(fromAddr) if err != nil { return fmt.Errorf("invalid envelope from address: %w", err) } @@ -136,7 +136,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { } for _, rec := range to { - smtpTo, err := sanitizeSMTPAddress(rec) + smtpTo, err := sanitizeEmailAddress(rec) if err != nil { return fmt.Errorf("invalid recipient address %q: %w", rec, err) } @@ -162,7 +162,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { return nil } -func sanitizeSMTPAddress(raw string) (string, error) { +func sanitizeEmailAddress(raw string) (string, error) { addr, err := mail.ParseAddress(strings.TrimSpace(strings.Trim(raw, "<>"))) if err != nil { return "", err diff --git a/services/mailer/sender/smtp_test.go b/services/mailer/sender/smtp_test.go new file mode 100644 index 0000000000000..1e944583a6ad0 --- /dev/null +++ b/services/mailer/sender/smtp_test.go @@ -0,0 +1,30 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package sender + +import "testing" + +func TestSanitizeEmailAddress(t *testing.T) { + tests := []struct { + input string + expected string + hasError bool + }{ + {"abc@gitea.com", "abc@gitea.com", false}, + {"", "abc@gitea.com", false}, + {"ssss.com", "", true}, + {"", "", true}, + } + + for _, tt := range tests { + result, err := sanitizeEmailAddress(tt.input) + if (err != nil) != tt.hasError { + t.Errorf("sanitizeEmailAddress(%q) unexpected error status: got %v, want error: %v", tt.input, err != nil, tt.hasError) + continue + } + if result != tt.expected { + t.Errorf("sanitizeEmailAddress(%q) = %q; want %q", tt.input, result, tt.expected) + } + } +}