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 3207eee32fcec..fa07803359cad 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 := sanitizeEmailAddress(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 := sanitizeEmailAddress(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 sanitizeEmailAddress(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 { 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) + } + } +}